贪心算法1
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
这么说有点抽象,来举一个例子:
例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
指定每次拿最大的,最终结果就是拿走最大数额的钱。
每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
贪心的主要思路就是通过手动模拟
455.分发饼干
每个孩子给一块饼干,并满足他的胃口,所以我们应该对排序完的孩子和饼干,每次都把最小的且能满足其胃口额度饼干分给那个孩子。
1 | // 版本一 |
376. 摆动序列
连续数字之间的差严格地在正数和负数之间交替,可以从原始序列中删除一些,所以我们的分析就是如何删除元素,图示举例我们删除元素删除单调的元素
局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
在计算是否有峰值的时候,大家知道遍历的下标 i ,计算 prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i]),如果prediff < 0 && curdiff > 0
或者 prediff > 0 && curdiff < 0
此时就有波动就需要统计。然后我们要做的分析情况
情况一:上下坡中有平坡
删左面三个 2 的规则,那么 当
prediff = 0 && curdiff < 0
也要记录一个峰值,因为他是把之前相同的元素都删掉留下的峰值。峰值记录规则改为(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)
情况二:数组首尾两端
可以假设,数组最前面还有一个数字,那这个数字应该是什么呢?
之前我们在 讨论 情况一:相同数字连续 的时候, prediff = 0 ,curdiff < 0 或者 >0 也记为波谷。
那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即 preDiff = 0,如图:
这样就解决了prediff 初始值的问题
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;
}
return result;
}
};情况三:单调坡中有平坡
但是上述的代码并不能退解决单调坡上有平坡的图,因为我们在实时的更新我们的pre导致最后是0和上升。
1 | class Solution { |
53. 最大子序和
找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
从代码角度上来讲:遍历 nums,从头开始用 count 累积,如果 count 一旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count,只会拖累总和。
1 | class Solution { |
122.买卖股票的最佳时机 II
这道题目可能我们只会想,选一个低的买入,再选个高的卖,再选一个低的买入…..循环反复。
如果想到其实最终利润是可以分解的,那么本题就很容易了!
所以我们就可以计算每一天的利润,如果是负就不买这样
或者可以画一个图,我们只去上升部分在最低点买,最高点卖,总结出就是
1 | class Solution { |
55. 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
所以我们的思路就是包括
比如我在位置0可以跳3步,那么我就要找保证位置0在内的0-3位置可以跳的最远的位置跳到那里去,然后继续计算,
1 | class Solution { |
45.跳跃游戏 II
请你使用最少跳数调到目的地
本题要计算最少步数,那么就要想清楚什么时候步数才一定要加一呢?
贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最少步数。
思路虽然是这样,但在写代码的时候还不能真的能跳多远就跳多远,那样就不知道下一步最远能跳到哪里了。
所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
1 | class Solution { |
1005.K次取反后最大化的数组和
- 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
1 | class Solution { |
134. 加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
很容易想到如果油量加起来是负我们就重新开始,因为如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
1 | class Solution { |
135. 分发糖果
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
那么遍历是用从后遍历还是从前遍历呢
如果左孩子比右孩子比较,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。
1 | class Solution { |