代码随想录刷题day28丨122.买卖股票的最佳时机II, 55. 跳跃游戏, 45.跳跃游戏II, 1005.K次取反后最大化的数组和
1.题目
1.1买卖股票的最佳时机II
-
文档讲解:https://programmercarl.com/0122.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAII.html
-
解题思路:贪心
-
如果想到其实最终利润是可以分解的,那么本题就很容易了!
-
如何分解呢?
假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。
相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。
此时就是把利润分解为每天为单位的维度,而不是从 0 天到第 3 天整体去考虑!
那么根据 prices 可以得到每天的利润序列:(prices[i] - prices[i - 1])…(prices[1] - prices[0])。
如图:
-
-
代码:
//时间复杂度:O(n) //空间复杂度:O(1) class Solution { public int maxProfit(int[] prices) { int result = 0; for(int i = 1;i < prices.length;i++){ result += Math.max(prices[i] - prices[i - 1],0); } return result; } }
-
总结:
-
至少要第二天才会有利润,所以利润的序列比股票序列少一天!
-
从图中可以发现,其实我们需要收集每天的正利润就可以,收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间。
-
那么只收集正利润就是贪心所贪的地方!
-
局部最优:收集每天的正利润,全局最优:求得最大利润。
-
本题中理解利润拆分是关键点! 不要整块的去看,而是把整体利润拆为每天的利润。
一旦想到这里了,很自然就会想到贪心了,即:只收集每天的正利润,最后稳稳的就是最大利润了。
-
1.2跳跃游戏
-
文档讲解:https://programmercarl.com/0055.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.html
-
解题思路:贪心
-
其实跳几步无所谓,关键在于可跳的覆盖范围!
-
不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。
-
这个范围内,别管是怎么跳的,反正一定可以跳过来。
-
那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!
-
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
-
-
代码:
//时间复杂度: O(n) //空间复杂度: O(1) class Solution { public boolean canJump(int[] nums) { int cover = 0;//覆盖下标 if(nums.length == 1){ return true; } for(int i = 0;i <= cover;i++){ cover = Math.max(i + nums[i],cover); if(cover >= nums.length - 1){ // 覆盖范围可以超过 return true; } } return false; } }
-
总结:
- 贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
1.3跳跃游戏II
-
文档讲解:https://programmercarl.com/0045.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8FII.html
-
解题思路:贪心
-
真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
-
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
-
如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。
-
-
图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)
-
代码:
class Solution { public int jump(int[] nums) { if(nums.length == 1){ return 0; } int cur = 0;//当前覆盖范围 int next = 0;//下一步覆盖范围 int result = 0;//记录步数 for(int i = 0;i < nums.length;i++){ next = Math.max(i + nums[i],next); if(i == cur){ if(cur != nums.length){ result++; cur = next; if(cur >= nums.length -1){ break; } }else{ break; } } } return result; } }
-
总结:
- 从图中可以看出来,就是移动下标达到了当前覆盖的最远距离下标时,步数就要加一,来增加覆盖距离。最后的步数就是最少步数。
- 这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
- 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
- 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
1.4 K次取反后最大化的数组和
-
视频讲解:贪心算法,这不就是常识?还能叫贪心?LeetCode:1005.K次取反后最大化的数组和_哔哩哔哩_bilibili
-
文档讲解:https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.html
-
解题思路:贪心
- 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
-
代码:
//排序数组并贪心地尽可能将负数翻转为正数,再根据剩余的k值调整最小元素的符号,从而最大化数组的总和。 class Solution { public int largestSumAfterKNegations(int[] nums, int k) { if(nums.length == 1){ return nums[0]; } // 排序:先把负数处理了 Arrays.sort(nums); for(int i = 0;i < nums.length && k > 0;i++){ //k > 0很关键 if(nums[i] < 0){ nums[i] = -nums[i]; k--; } } if(k % 2 == 1){ Arrays.sort(nums); nums[0] = -nums[0];//把最小的正数取反 } int sum = 0; for(int i = 0;i < nums.length;i++){ sum += nums[i]; } return sum; } }
-
总结:
- 如果没有贪心的思考方式(局部最优,全局最优),很容易陷入贪心简单题凭感觉做,贪心难题直接不会做,其实这样就锻炼不了贪心的思考方式了。