# 贪心算法理论基础
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
贪心算法一般分为如下四步:
- 将问题分解为若干子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
# 分发饼干
题目链接🔗
大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
class Solution { | |
public: | |
int findContentChildren(vector<int>& g, vector<int>& s) { | |
sort(g.begin(), g.end()); | |
sort(s.begin(), s.end()); | |
int index = s.size() - 1; // 饼干数组的下标 | |
int result = 0; | |
for (int i = g.size() - 1; i >= 0; i--) { // 遍历胃口 | |
if (index >= 0 && s[index] >= g[i]) { // 遍历饼干 | |
result++; | |
index--; | |
} | |
} | |
return result; | |
} | |
}; |
# 摆动序列
题目链接🔗
用 [1,17,5,10,13,15,10,5,16,8] 来举例:
局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
因为题目要求大的是最长摆动子序列长度,所以只统计峰值数量即可。
但要考虑两张异常情况: 上下中间有平坡、单调有平坡。
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; | |
} | |
}; |
# 最大子序和
题目链接🔗
局部最优:当前 “连续和” 为负数的时候立刻放弃,从下一个元素重新计算 “连续和”,因为负数加上下一个元素 “连续和” 只会越来越小。
全局最优:选取最大 “连续和”
局部最优的情况下,并记录最大的 “连续和”,可以推出全局最优。
红色的起始位置就是贪心每次取 count 为正数的时候,开始一个区间的统计。
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; | |
} | |
}; |