代码随想录:贪心算法
分发饼干
|
|
本题可以使用贪心算法。我们要获取尽量多的饼干和胃口匹配的数量,先将胃口数组g和饼干数组s进行排序,然后让大饼干尽量去匹配大胃口。
如果当前饼干不满足当前的胃口,则缩小胃口值(指针位置左移)。
双重循环遍历时,外层遍历的是胃口数组,因为我们要用饼干去匹配胃口,当匹配时,缩小胃口值。
|
|
摆动序列
较难,建议多加练习。
|
|
本题需要计算 nums 数组的最长摆子序列,计算要计算 nums 中有多少个峰值(局部最大和局部最小)。
可以考虑使用贪心算法,当 nums[i-1]-nums[i]<0 && nums[i+1]-nums[i]>0或者 nums[i-1]-nums[i]>0&&nums[i+1]-nums[i]<0(i 位置的左右差值一正一负数)时,就是我们需要统计的位置。
但我们还需要考虑出现平坡的情况,参考:0376.摆动序列。
如果左边 i 的左侧位置出现平坡(非单调平坡),那么我们只统计最后一个平坡的位置,也就是 nums[i-1]-nums[i]<=0 && nums[i+1]-nums[i]>0(添加了左边等于 0 的情况),或者 nums[i-1]-nums[i]>=0&&nums[i+1]-nums[i]<0(添加了左边等于 0 的情况)。
但还有可能出现单调平坡的情况,也就是差值先为正,然后出现部分 0,然后再继续为正,而不是负,这时候我们不应该统计这样的平坡。
在代码中,我们使用 preDiff 表示i前面的差值,postDiff 表示 i 后面的差值
我们可以在出现非单调平坡时,才更新 preDiff 的值为 postDiff(当出现了转折才更新),这样当出现单调平坡时,也不会被统计了。
|
|
最大子数组和
|
|
本题使用贪心算法,需要明确我们“贪”的是哪里。
不是遇到负数就跳过该元素,而是:如果“连续和”为负数,我们就放弃当前得到的“连续和”,从下一个位置重新开始计算“连续和”,因为前面小于 0 的“连续和”只会拖累我们的计算,让我们得到值更小,还不如重新开始计算。
|
|
买卖股票的最佳时机II
|
|
我们可以计算每天可以获得的利润,如果利润大于 0,则将该利润加到 result 中。
|
|
跳跃游戏
|
|
本题我们不需要关注每一步跳跃的长度,而是要关注当前的覆盖范围,然后使用贪心算法来获取我们当前可以获取到的最大覆盖范围。如果覆盖范围包括了数组的最后位置,那么就返回 true。
|
|
跳跃游戏II
本题较为复杂,建议多加练习。
|
|
本题和 跳跃游戏 一样,我们不关心每一步具体是怎么跳跃的,我们只关心最少多少步可以调到末尾。
我们可以遍历当前的覆盖范围,在遍历当前覆盖范围时,记录下一步的最大覆盖范围(next)。
如果遍历当前的覆盖范围到尽头了,还没有到最后,就要增加步数,启动下一步的覆盖范围。
|
|
K次取反后最大化的数组和
|
|
先对 nums 数组进行自定义排序(按照绝对值从大到小排序)。
为什么要绝对值从大到小呢?因为我们的贪心算法要尽量让最大的负数变成正数,所以应该先遍历大的负数。
然后遍历数组,如果 nums[i]<0 且 k 大于 0,则将该负数变为正数,然后 k–。
如果遍历完了数组之后,还有剩余的 k(说明数组全部都变成非负数了)。
判断 k 是奇数还是偶数,如果是奇数,则将数组中最小的元素(最后一个元素)变成它的相反数。
如果 k 小于数组中负数的个数,最后的 if 语句也不会执行(因为 k==0)。
|
|
加油站
|
|
本题贪心算法的思路:遍历 gas 和 cost,计算二者的差值,得到当前剩余的油量 curSum。
如果累计的油量 curSum 小于0,那么我们就选择下一个位置作为起始位置,因为前面的剩余油量为负数,只会拖累我们,还是不如重新开始。
在遍历过程中,使用 totalSum 来累计每个位置剩余的油量( gas[i] 和 cost[i] 的差值),这里把两个 for 循环合成了一个 for 循环。
如果遍历完成之后,发现 totalSum 小于 0,说明总油量小于总消耗,返回 -1。
|
|
分发糖果
|
|
本题我们需要保证:相邻两个孩子评分更高的孩子会获得更多的糖果。
这就需要比较 i 位置和 i-1 位置和 i+1 位置的评分,然后分配糖果。
为了防止分配的混乱,我们需要用两个 for 循环来进行比较,先比较左边的,再比较右边的。
我们使用 candyArr 数组保存每个位置分配的糖果数量。
第一个 for 循环,i 从 1 开始,如果 ratings[i]>ratings[i-1],那么 candyArr[i]=candyArr[i-1]+1。 第二个 for 循环,由于是比较 i 和 i+1,为了能复用之前计算得到的糖果数量,我们就必须从右边开始,然后往左边移动。
candyArr[i] 位置真正的糖果数量就应该取两次循环的得到的较大值。
最后计算 candyArr 数组的总和。
|
|
柠檬水找零
|
|
本题需要分析我们遇到的各种币值,并统计我们现在手中有的各种币值的数量。
|
|
根据身高重建队列
|
|
每个一维数组有两个维度,一个是身高 h,一个是 k,表示它前面有多少个大于等于它的元素。
我们可以先对二维数组进行排序(排序规则为先按照 h 降序,如果 h 相同,再按照 k 升序)
然后再遍历二维数组,将一维数组根据 k 的值,插入到它应该在的位置。
由于 i 位置元素前面的元素 h 都比它大,所以 k 的位置就是二维数组的下标。
|
|
用最少数量的箭引爆气球
|
|
本题需要明确什么时候气球与气球之间存在重叠。
我们先对 points 数组按照左边界进行排序(方便我们的遍历过程)
当一个气球的左边界小于等于前一个气球的右边界时,表示两个气球存在重叠。
那我们如何确定需要使用的弓箭数呢?
如果气球不重叠,则弓箭数量加 1。
如果当前气球和前一个气球重叠了,那我们就要将当前气球的右边界更新为:当前气球的右边界和前一个气球的右边界中的较小值。
因为这个范围可以被同一个弓箭射中,而且我们在前面已经统计了该弓箭,我们只需要更新右边界的值即可。
|
|
无重叠区间
|
|
本题和 用最少数量的箭引爆气球 一样,要对区间重叠进行判断。
先按照 intervals 的左边界排序。
如果 intervals[i] 的左边界大于等于 intervals[i-1] 的右边界,说明二者是不重叠的,否则,二者是重叠的。
如果发生了重叠,我们要记录重叠的右边界(将当前元素的右边界更新为重叠部分的右边界)。
我们统计了重叠区间的数量,只要删除这些重叠区间,就可以让整个数组没有重叠区间了,所以重叠区间的数量就是要删除的数量。
|
|
划分字母区间
|
|
本题需要先遍历字符串 s,记录 s 中每个字母出现的最远位置(index 最大)
然后再次变量 s,获取当前区间(第 0 个元素到出现过的元素的最远位置)字母的最远位置的最大值 right。
使用 left 记录当前区间的最左侧。
如果 i==right,说明该区间已经把所有的出现过的字母都包含了,将该区间长度添加到 res 中。
|
|
合并区间
|
|
本题我们也需要判断区间是否重叠,我们需要将重叠的区间合并,先按照区间的左侧值进行排序,然后合并重叠区间。
|
|
单调递增的数字
|
|
先将 n 转换为字符串 str,然后从后往前遍历 str。
如果 str[i]>str[i+1],就将 i 位置的值减去 1,并用 flag 标识 i 位置,flag 位置后面的位置都应该设置为 9。
|
|
监控二叉树
本题过于复杂,可以先战略性跳过。