您现在的位置是:首页 >学无止境 >贪心算法OJ刷题(1)网站首页学无止境
贪心算法OJ刷题(1)
贪心算法
指所求问题的整体最优解可以通过一系列局部最优的选择来到达。是不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,它的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
该算法不能求解最值问题。
选择排序
它所采用的贪心策略即为每次从未排序的数据中选取最小值,并把最小值放在未排序数据的起始位置,直到未排序的数据为0,则结束排序。
在数据结构中我写的选择排序是经过优化的,一次找两个数(最大值和最小值)。但是思路是一样的。
void swap(int* array, int i, int j)
{
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
void selectSort(int* arr, int n){
int minIndex = 0;
// i: 未排序数据的起始位置
for(int i = 0; i < n; i++)
{
// 从所有未排序的数据中找最小值的索引
for(int j = i + 1; j < n; j++)
{
if(arr[j] < arr[minIndex]) minIndex = j;
}
swap(arr, i, minIndex);
}
}
分割平衡字符串
题目描述
平衡字符串 中,‘L’ 和 ‘R’ 字符的数量是相同的。给你一个平衡字符串 s,请你将它分割成尽可能多的子字符串,并满足:每个子字符串都是平衡字符串。
返回可以通过分割得到的平衡字符串的 最大数量 。
解题思路
题目要求通过分割得到平衡字符串的最大数量,即尽可能多的分割。
贪心策略: 不要有嵌套的平衡串,只要目前字符串达到平衡,就立即分割。故可以定义一个变量balance,在遇到不同的字符时,向不同的方向变化,当balance为0时达到平衡,分割数更新。
比如:从左往右扫描字符串s,遇到R, balance - 1;遇到L,balance + 1;当balance为0时即,更新记录cnt ++;如果最后cnt==0,说明s只需要保持原样,返回1
class Solution {
public:
int balancedStringSplit(string s) {
//贪心策略:遇到平衡的就++,不嵌套
int count = 0;
int balance = 0;
for(auto &e: s)
{
if(e == 'L') ++balance;
else --balance;
if(balance == 0) ++count;
}
return count;
}
};
买卖股票的最佳时机 II
题目描述
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
解题思路
连续上涨交易日: 则第一天买最后一天卖收益最大,等价于每天都买卖
连续下降交易日: 则不买卖收益最大,即不会亏钱。
故可以遍历整个股票交易日价格列表,在所有上涨交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//贪心策略:如果第2天比第1天的高,就在第1天买入第二天卖出。低的话就不操作
int money = 0;
for(int i = 1; i < prices.size(); ++i)
{
if(prices[i-1] < prices[i])
{
money += (prices[i] - prices[i-1]);
}
}
return money;
}
};
跳跃游戏
题目描述
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
解题思路
设想一下,对于数组中的任意一个位置 y,我们如何判断它是否可以到达?根据题目的描述,只要存在一个位置 i,它本身可以到达,并且它跳跃的最大长度为 i + nums[i],这个值大于等于 y,即 i+nums[i] ≥ y,那么位置y也可以到达。换句话说,对于每一个可以到达的位置 x,它使得 i+1, i+2,⋯, i+nums[i] 这些连续的位置都可以到达。
依照上述思路,我们依次遍历数组中的每一个位置,并实时更新最远可以到达的位置。对于当前遍历到的位置i,如果它在 最远可以到达的位置的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 下标i+nums[i]更新 最远可以到达的位置。
在遍历的过程中,如果最远可以到达的位置大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接返回True作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxNum = 0;//目前能到的最远的地方
int n = nums.size();
bool can = false;//是否走到目的地
for(int i = 0; i < n; ++i)
{
//每走一步就更新最远能到的下标
if(maxNum >= i)
{
maxNum = max(maxNum, i + nums[i]);
}
// 如果最大能够到达的位置已经到达数组末尾,说明必然可以到达
if(maxNum >= n-1)
{
can = true;
break;
}
// 如果当前最远可以到达的位置未超过当前位置,肯定无法到达数组末尾
if(maxNum < i)
{
break;
}
}
return can;
}
};
纸币找零
题目描述
假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,最少可以用多少张纸币完成支付?
vector<vector> = { {面值, 张数}, {面值, 张数}, … };
解题思路
用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。
#include<iostream>
#include<algorithm>
using namespace std;
int solve(int money,const vector<pair<int,int>>& moneyCount)
{
int num = 0;
//首先选择最大面值的纸币
for (int i = moneyCount.size() - 1; i >= 0; i--)
{
//需要的当前面值数量 与 原面值数量 取最小
//比如张数为0时,就不可以取该面值的纸币
int c = min(money / moneyCount[i].first, moneyCount[i].second);
money = money - c * moneyCount[i].first;
num += c;
}
if (money > 0)//说明现有纸币无法完成交易
num = -1;
return num;
}
int main()
{
//存放纸币与数量: first:纸币,second:数量
vector<pair<int, int>> moneyCount = { { 1, 3 }, { 2, 1 }, { 5, 4 }, { 10, 3 }, { 20, 0 }, {50, 1}, { 100, 10 } };
int money;
cout << "请输入要支付的钱" << endl;
cin >> money;
int res = solve(money, moneyCount);
if (res != -1)
cout << res << endl;
else
cout << "NO" << endl;
return 0;
}