理论基础

贪心算法其实就是没有什么规律可言。没有思路就立刻看题解。
基本贪心的题目有两个极端,要不就是特简单,要不就是死活想不出来。

贪心的本质:贪心的本质是选择每一阶段的局部最优,从而达到全局最优。

什么时候用贪心

贪心算法并没有固定的套路。就是常识性推导加上举反例子。刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心。

贪心一般解题步骤

做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。

1、455分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。

我的思路:我想每个人在饼干里找离他最能饱饼干,把饼干排序,这样查找时快一些。测试了一下 21/25超时。结果应该是对的,但是超时。于是我把食量也排序,这样找过的饼干前面的就不看了,23/25超时还是超时。
代码随想录:人从后往前遍历找饼干。简便了很多。下图1是代码随想录的,从后往前只需看当前饼干能不能满足孩子胃口,不能那这个孩子就不用看了,往前的饼干也不可能满足。很经典。。图1是我的,从前往后,当前饼干不满足孩子胃口,但是后面的饼干更大,可能满足,我还得接着遍历饼干,时间复杂度更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 23/25 超时
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
vector<bool> used(s.size(),false);
std::sort(g.begin(),g.end());
std::sort(s.begin(),s.end());
int num = 0;
int lastj = 0;
for(int i =0;i<g.size();i++){
for(int j = lastj;j<s.size();j++){
// if(used[j] == true)continue;
if(s[j] < g[i])continue;
used[j] = true;
num++;
lastj = j+1;
break;
}
}
return num;
}
};
// 看了代码随想录后我写的
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
std::sort(g.begin(),g.end());
std::sort(s.begin(),s.end());
int num = 0;
for(int i = g.size()-1, j = s.size()-1;i>=0;i--){
if(j < 0 || s[j] < g[i])continue;
num++;
j--;
}
return num;
}
};

2、376摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

我的思路:没做出来。这题暂时不做了。贪心。
代码随想录:局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 代码随想录
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
int curDiff = 0; // 当前一对差值
int preDiff = 0; // 前一对差值
int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值
for (int i = 0; i < nums.size() - 1; i++) {
curDiff = nums[i + 1] - nums[i];
// 出现峰值
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
result++;
preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff
}
}
return result;
}
};

3、53最大子序和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6

我的思路:没做出来。贪心真的想不到。只能用用回溯做。
代码随想录:局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。全局最优:选取最大“连续和”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 代码随想录
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT32_MIN;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
result = count;
}
if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return result;
}
};