今天进入贪心算法的学习。之前什么回溯模板、递归三部曲啥的,都是有一定代码框架和套路的。我们希望能够总结出一套贪心算法的套路
那么贪心算法的套路就是:贪心算法没有任何代码套路
只需要明确核心思想:贪心的本质是选择每一阶段的局部最优,从而达到全局最优。思考的时候稍微注意一下局部最优和全局最优是什么就行
贪心算法解题不需要证明,因为每道题的证明方法都不一样,如果一道题觉得贪心能做就尝试去做,AC了就行,AC不过就说明贪心思路不行
455.分发饼干
思路:为了满足更多的小孩,就不要造成饼干尺寸的浪费
大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的
这里的局部最优就是大饼干喂给胃口大的,小胃口吃小饼干,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩
上代码
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//主打一个减少浪费:胃口最小的孩子用最小的饼干去满足
sort(g.begin(), g.end());
sort(s.begin(), s.end()); // 将饼干尺寸和胃口从小到大排序
int res = 0; // 记录已被满足的小孩子数量
int i = 0, j = 0;
for (i = 0; i < s.size(); ++i) { // 遍历饼干,尝试用饼干依次去满足小孩子
if (j < g.size() && s[i] >= g[j]) //孩子j的胃口能被满足
{
res++;
j++; //试着去满足下一个孩子
}
//如果不能满足孩子j,for循环会使饼干尺寸增大,进一步尝试满足这个孩子
}
return res;
}
};
376.摆动序列
问题转化一下就是:求原序列的波峰和波谷的个数
一个思路
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
auto iter = unique(nums.begin(), nums.end());
nums.erase(iter, nums.end()); // 先对数组进行去重
// 下面表述针对去重后的数组
if (nums.size() == 1) return 1; // 如果仅有一个元素,则返回1
if (nums.size() == 2) return 2; // 如果仅有两个元素,则返回2
int res = 2; // 只要大于等于两个元素,则首尾至少为摆动序列,至少为2
for (int i = 1; i < nums.size() - 1; ++i)
{ // 遍历首位之外的元素,依次判断其是否为波峰或者波谷
if ((nums[i] - nums[i - 1])*(nums[i + 1] - nums[i]) < 0) ++res;
}
return res;
}
};
53. 最大子序和
局部最优:从数组第一个元素开始取子序列,并计算连续和。若当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小
全局最优:选取最大“连续和”
代码思路:遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。同时,result应该记录出现过的最大count
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT32_MIN; // 用于记录“出现过的最大count”
int count = 0; // 连续和
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
if (count > result) { // result应该记录“出现过的最大连续和”
result = count;
}
if (count < 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return result;
}
};
此外,本题其实可以动态规划做,我们在下一章会深入探讨动态规划
回顾总结
感觉贪心部分的题目有点儿像脑筋急转弯,能不能做出来全看一个缘分