贪心_20230415
刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心
贪心没有套路,说白了就是常识性推导加上举反例。
376. 摆动序列
题目说明
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
解题思路1:贪心
用res
来记录最终的最长摆动序列的长度,将其初始化为1(已说明nums数组长度至少为1)
通过prediff
和curdiff
两个变量来记录之前的差值和现在的差值,当两个差值异号时则说明数列发生了摆动,则更新差值(主要是为了更新差值的符号),并将res + 1,摆动序列长了一个单位
对于prediff
需要进行思考处理。首先,当一开始遍历是prediff应该初始化为0,可以理解为在此之前还有一个和nums[0]相同的数字,此时的差值为0而最长长度为1。在后续的遍历过程中可能出现一种情况,就是一开始连续的几个数字都是相同的,直到curdiff出现不为0的情况,此时prediff仍为0,这种情况只可能发生在nums的头部,因此当prediff == 0 && (curdiiff != 0)时实际上是发生了摆动的,因此res要加1;第二种情况是,在过程中出现了连续几个相同的数字,即prediff不为0而curdiff为0,此时什么都不要做,prediff仍记录之前的摆动趋势,直到curdiff出现了相反趋势再做处理。两种情况合并起来如下:
if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0)) {
prediff = curdiff;
res++;
}
解题思路2:动归
动态规划需要我们对初始的情况进行额外的处理,即nums.length == 1
和nums.length == 2
,后者还要求我们对nums[0]和nums[1]进行比较处理
在处理波动时分为几种情况:
prediff == 0 && curdiff != 0
:这是对序列头部是否连续相同的一个额外处理,即序列发生第一次波动,dp[i] = dp[i - 1] + 1,更新prediffprediff * curdiff < 0
:说明序列在过程中出现了波动,dp[i] = dp[i - 1] + 1,更新prediff- 其余情况则没有发生波动,dp[i] = dp[i - 1]即可
最终结果即是dp[dp.length - 1]
53. 最大子序和
题目说明
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
- 输入: [-2,1,-3,4,-1,2,1,-5,4]
- 输出: 6
- 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
解题思路1:贪心
用max记录最终的结果,用count来遍历过程的子序列和。当count遇到负数时也可以加上去,因为可能加上下一个数字后变得更大;当count < 0时,如果遇到负数那就变得更小,遇到正数时nums[i]当然比count大,因此当count小于0时可以将其置为0,视为重新更新起点。
解题思路2:动归
通过max和dp[i]比较记录最终结果,通过dp来遍历数组。dp[i]的含义是:以nums[i]为结尾的序列(包含nums[i])的左区间序列最大和是多少。(如果通过一个额外的dp_right[]
来记录后相加得到的话,意思是这一段区间的每个数的最大序列和是多少,本题不需要那么复杂,找到那个值就可以了)。
55. 跳跃游戏
题目说明
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例 1:
- 输入: [2,3,1,1,4]
- 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
- 输入: [3,2,1,0,4]
- 输出: false
- 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
解题思路
应用贪心
局部最优解:每次取最大跳跃步数(取最大覆盖范围)
整体最优解:最后得到整体最大覆盖范围,看是否能到终点
- 要注意的是,到达的是最后一个位置,即下标为
nums.length - 1
- 只要判定最后能到达的最远位置即可,用cur来记录当前可以到达的最远位置。从0开始遍历,在遍历的过程中不断的更新cur:
cover = Math.max(i + nums[i], cover);
for (int i = 0; i <= cover; i++) {
点击查看代码
for (int i = 0; i <= cover; i++) {
cover = Math.max(i + nums[i], cover);
if (cover >= nums.length - 1) {
return true;
}
}
45.跳跃游戏II
题目说明
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
- 输入: [2,3,1,1,4]
- 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
解题思路
和55. 跳跃游戏不同之处在于,此题保证可以到达最后一个位置,所以只需要考虑怎么用最快的方法往前跳就可以了。根据贪心的思路,局部最优解不是说当前这一步能跳到哪就是最远,而是在当前位置cur
到cur + nums[cur]
找到可以跳的最远情况,通过count来记录跳数
- 当num.length == 1直接返回0,根本不用跳
- 判定结束时可以通过
cur + nums[cur] >= nums.length - 1
,这意味着再跳一步就可以了,可以结束循环。所以最终返回的结果要+1 - 判定落脚点则通过遍历[cur, cur + nums[cur]]这一段,通过中间变量来记录最远可以跳到哪里
i + nums[i]
,并记录下这个i,用来更新cur