摘要
本文主要介绍了贪心算法的理论基础,以及LeetCode贪心算法的几个题目,包括455.分发饼干、376. 摆动序列、53. 最大子序和。
1、贪心算法理论基础
1.1 概念
什么是贪心
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
什么时候用贪心
说实话贪心算法并没有固定的套路。
最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。
贪心一般解题步骤
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。
做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。
2、455.分发饼干
2.1 思路
- 局部最优:优先满足胃口大的孩子
- 排序+双指针+贪心
- 首先,对孩子的胃口和饼干的大小进行排序,从小到大
- 接下来,我们采用贪心策略,尽量先满足胃口大的孩子
- 我们从胃口最大的孩子开始,尝试匹配最大的饼干
- 如果当前饼干满足了孩子的胃口,我们将数量+1,并尝试下一个孩子和饼干
- 如果当前饼干不满足孩子的胃口,我们尝试下一个孩子,继续匹配当前饼干
- 以此类推,直到所有孩子或所有饼干都被考虑完
2.2 代码
public int findContentChildren(int[] g, int[] s) {
int count = 0;
Arrays.sort(g);
Arrays.sort(s);
int j = s.length-1;
for(int i=g.length-1; i>=0; i--) {
if(j >=0 && s[j] >= g[i]) {
j--;
count++;
}
}
return count;
}
3、376. 摆动序列
3.1 思路
- 初始化摆动序列的长度
count为1,因为序列中至少有一个元素。 - 定义
preDiff和curDiff分别表示前一个元素与当前元素之间的差值,以及当前元素与后一个元素之间的差值。 - 如果
(preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)的条件成立,说明出现了摆动变化,摆动序列的长度就会增加1。 - 在摆动变化发生时才更新
preDiff。
这种贪心算法的核心思想是只关注摆动变化的情况,并且通过维护
preDiff来判断是否出现了摆动。这样可以在一次遍历中找到最长的摆动序列长度。
3.2 代码
public int wiggleMaxLength(int[] nums) {
int count = 1;
int preDiff = 0;
int curDiff = 0;
for(int i=0; i<nums.length-1; i++) {
curDiff = nums[i+1] - nums[i];
if((preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)) {
count++;
preDiff = curDiff;
}
}
return count;
}
4、53. 最大子序和
4.1 思路
- 初始化两个变量
maxSum和currentSum,分别用于跟踪全局最大子数组和和当前子数组和。 - 遍历整个数组,从第一个元素开始。
- 对于每个元素,将其添加到当前子数组的末尾,并更新
currentSum为当前子数组的和。 - 在每一步中,比较
currentSum和maxSum,并将currentSum更新为它们中的较大值。 - 如果
currentSum变成负数,说明当前子数组的和不再对后续元素的和产生积极影响,因此我们可以放弃当前子数组,将currentSum重置为0,重新开始构建新的子数组。 - 继续遍历数组,重复步骤 3 到步骤 5,直到遍历完整个数组。
- 最终,
maxSum中存储的值即为最大子数组和。
4.2 代码
public int maxSubArray(int[] nums) {
int maxSum = Integer.MIN_VALUE;
int curSum = 0;
for(int i=0; i<nums.length; i++) {
curSum += nums[i];
maxSum = Math.max(maxSum, curSum);
if(curSum < 0) {
curSum = 0;
}
}
return maxSum;
}