贪心算法理论基础
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
什么时候用贪心
如果局部最优👉整体最优 验证方法:
- 举反例
- 反证法
- 数学归纳法
贪心解题步骤(可能没用
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
LeetCode 455 分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。
思路
为了满足尽可能多的孩子,我们应该优先给胃口小的孩子饼干,并且使用尽可能小的饼干
算法
- 对孩子的胃口g和饼干尺寸s非递减排序,得到满足的孩子个数count=0
- 对g和s用指针i和j遍历
- 对每个g[i],不断向后移动j直到出现一个
s[j] >= g[i],count+1
- 对每个g[i],不断向后移动j直到出现一个
解法
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int i = 0;
int j = 0;
int count = 0;
for (; i < g.length && j < s.length; i++) {
while (j < s.length && s[j] < g[i]) {
j++;
}
if (j >= s.length) {
break;
}
else {
count++;
}
j++;
}
return count;
}
}
LeetCode 376 摆动序列
思路
需要删除一些元素得到摆动序列,我们考虑删除何种元素:
- 整体最优:序列上有最多的局部峰值
- 局部最优:只保留单调坡度两端的节点
如何判断局部峰值? 字面意义上,在递减区出现的最小值和在递增区出现的最大值
序列两端如何处理? 由于仅有一个元素或者含两个不等元素的序列也视作摆动序列,长度分别为1和2,我们可以单独处理。 且我们考虑任意一个长度大于2的序列,其长度必定大于等于2.因为根据我们局部峰值的判断,两端都是局部峰值。
平坡如何处理?
- 对于一个单调坡➕平坡,等同于一个单调坡。但这个单调坡必须在平坡之前,这是为了确定单调坡的方向,提前知道这个合并后的单调坡是什么类型,避免一个纯平坡被判断为单调坡
- 对于纯平坡,视为一个数字,所以只需要移动指针,不需要处理别的。直到遇到下一个单调坡或者索引不合法。
- 如果遇到下一个单调坡,此时指针的位置就是单调坡的起点,这和直接进入单调坡+平坡情况相同
- 如果索引不合法,由于平坡的最后一个数字不能加入结果,所以也不需要处理
- 举例说明,如果序列为[1,2,2,1],我们看作一个递增区加[1,2,2]一个递减区[2,1]。如果序列为[1,2,2,2]我们看作一个递增区,如果序列为[2,2,2,1],我们看作一个平坡➕一个递减区
算法
- 结果result初始化为1
- 如果序列长度为1或长度为2且元素相等,返回
- 如果长度等于2,返回2
- 初始化指针i为1,当i合法
- 如果nums[i]>nums[i-1]说明在递增区
- while nums[i]>=nums[i-1],且i合法,i+1
- 退出while说明进入了递减区或到达序列右端,不论哪种情况我们都找到了局部峰值,result+1
- 如果i不合法,返回result
- 如果
nums[i]==nums[i-1],且i合法,说明在平区,i+1 - 如果i不合法,返回result
- 如果nums[i]<nums[i-1],说明在递减区
- while nums[i]=<nums[i-1],且i合法,i+1
- 退出while说明进入了递增区或到达序列右端,不论哪种情况我们都找到了局部峰值,result+1
- 如果nums[i]>nums[i-1]说明在递增区
解法
class Solution {
public int wiggleMaxLength(int[] nums) {
int result = 1;
if (nums.length == 1) {
return 1;
}
if (nums.length == 2) {
if (nums[1] == nums[0]) {
return 1;
}
else {
return 2;
}
}
int i = 1;
while (i < nums.length) {
if (nums[i] > nums[i-1]) {
while (i < nums.length && nums[i] >= nums[i-1]) {
i++;
}
result++;
}
if (i >= nums.length) {
break;
}
while (i < nums.length && nums[i]==nums[i-1]) {
i++;
}
if (i >= nums.length) {
break;
}
if (nums[i] < nums[i-1]) {
while (i < nums.length && nums[i] <= nums[i-1]) {
i++;
}
result++;
}
}
return result;
}
}
解释
这个代码结构可以理解为,当区间类型改变时,选择一个while循环进入,不同的循环分别代表递增区(含递增区+平坡),纯平坡,递减区(含递减区+平坡)
LeetCode 53 最大子序和
思路
- 局部最优:抛弃和为负的子序列,解决子序列开始的问题。为了得到最大子序和,我们还要记录下历史最大和
- 全局最优:尽量少选择负数,多选择正数
算法
- sum=0,maxSum=0
- 遍历每个元素nums[i]
- 如果sum<0,重置sum为0
- sum+=nums[i]
- 更新maxSum
解法
class Solution {
public int maxSubArray(int[] nums) {
int sum = 0;
int maxSum = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
if (sum < 0) {
sum = 0;
}
sum += nums[i];
if (sum > maxSum) {
maxSum = sum;
}
}
return maxSum;
}
}
今日收获总结
今日学习时长2小时,贪心算法真的挺没规律的,多练题!