文章目录
- day32学习内容
- 一、买卖股票的最佳时机II
- 1.1、思路
- 1.2、代码-正确写法
- 1.2.1、如何理解result += Math.max(prices[i] - prices[i - 1], 0)
- 算法逻辑
- 二、跳跃游戏
- 2.1、思路
- 2.2、正确写法1
- 2.2.1、 如何理解上面这段代码
- 逻辑详解
- 2.2.2、为什么要i + nums[i]?
- 解释
- 为什么重要
- 2.2.3、还是看不懂?举个具体例子帮助理解
- 三、跳跃游戏II
- 3.1、思路
- 3.2、正确写法1
- 3.2.1、如何理解这段代码
- 3.2.2 、如何理解i == end
- 总结
- 1.感想
- 2.思维导图
day32学习内容
day32主要内容
- 买卖股票的最佳时机II
- 跳跃游戏
- 跳跃游戏II
声明
本文思路和文字,引用自《代码随想录》
一、买卖股票的最佳时机II
122.原题链接
1.1、思路
假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。
相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。
也就是prices[3] - prices[0] = 每天的利润之和。
只收集每天的正利润,收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间。
此题贪心在哪里?只收集正利润就是贪心所贪的地方!
局部最优:收集每天的正利润,全局最优:求得最大利润。
1.2、代码-正确写法
class Solution {public int maxProfit(int[] prices) {int result = 0;for (int i = 1; i < prices.length; i++) {// 只取正利润,和0取最大值,是负数直接加0,是正数就加prices[i] - prices[i - 1]result += Math.max(prices[i] - prices[i - 1], 0);}return result;}
}
1.2.1、如何理解result += Math.max(prices[i] - prices[i - 1], 0)
算法逻辑
int result = 0;
: 初始化变量result
用于累积总利润,初始值设为0。for (int i = 1; i < prices.length; i++) { ... }
: 通过一个循环遍历prices
数组(从第二个元素开始,即i = 1
),比较连续两天的价格,以决定是否进行交易。result += Math.max(prices[i] - prices[i - 1], 0);
: 这行代码是算法的核心。它计算连续两天的价格差(prices[i] - prices[i - 1]
),如果价格差是正的,表示第i-1
天买入、第i
天卖出可以获得利润,所以将这个正的价格差累加到result
中;如果价格差是负的或零,表示没有利润(或亏损),则不进行操作(通过Math.max(…, 0)
实现,即如果价格差小于0,则加0,否则加价格差)。
二、跳跃游戏
55.原题链接
2.1、思路
此段文字摘抄字代码随想录
https://www.programmercarl.com/0055.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.html#%E6%80%9D%E8%B7%AF
刚看到本题一开始可能想:当前位置元素如果是 3,究竟是跳一步呢还是两步呢还是三步呢,究竟跳几步才是最优呢?
其实跳几步无所谓,关键在于可跳的覆盖范围!
不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。
这个范围内,不用管是怎么跳的,反正一定可以跳过来。
那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
2.2、正确写法1
class Solution {public boolean canJump(int[] nums) {if (nums.length == 1) {return true;}// 初始化覆盖范围,为什么为0?因为开始没有调,所以是0int coverRange = 0;// 在覆盖范围内更新coverRangefor (int i = 0; i <= coverRange; i++) {// 当前为位置i,最大可达位置为i+nums[i]coverRange = Math.max(coverRange, i + nums[i]);if (coverRange >= nums.length - 1) {return true;}}return false;}
}
2.2.1、 如何理解上面这段代码
逻辑详解
-
特殊情况处理:
if (nums.length == 1) { return true; }
: 如果数组长度为1,意味着我们已经在最后一个位置上,不需要任何跳跃就已经达成条件,因此返回true
。
-
初始化覆盖范围:
int coverRange = 0;
: 初始化变量coverRange
,表示当前能覆盖到的最远距离。初始值为0,因为我们还没开始跳跃。
-
循环更新覆盖范围:
for (int i = 0; i <= coverRange; i++) {...}
: 遍历数组nums
,但只在当前覆盖范围内进行遍历。这是因为,如果某个位置超出了当前的覆盖范围,那么这个位置就无法被到达。coverRange = Math.max(coverRange, i + nums[i]);
: 在每一步,更新coverRange
。这里i + nums[i]
计算的是如果从位置i
进行跳跃,最远能到达的位置。Math.max
确保coverRange
总是存储当前能到达的最远位置。if (coverRange >= nums.length - 1) { return true; }
: 如果在任何时刻coverRange
超过或等于数组的最后一个位置的索引,那么说明可以到达数组的最后一个位置,方法返回true
。
-
无法到达最后位置:
- 如果循环结束,意味着即使尽可能地跳跃,也无法覆盖到数组的最后一个位置,此时方法返回
false
。
- 如果循环结束,意味着即使尽可能地跳跃,也无法覆盖到数组的最后一个位置,此时方法返回
2.2.2、为什么要i + nums[i]?
在这个问题中,i + nums[i]
的计算是为了确定从当前位置i
开始,最远能跳到哪里。这里的逻辑是基于题目的规则:你在位置i
时,可以跳跃的最大长度是nums[i]
。因此,如果你现在处于位置i
,那么你最远可以跳到i + nums[i]
这个位置。
解释
i
(当前位置): 表示你现在所在数组的位置。nums[i]
(跳跃能力): 数组nums
中的每个元素表示从那个位置最多可以向前跳跃的步数。因此,nums[i]
表示从位置i
最多可以跳跃的步数。i + nums[i]
(最远可达位置): 当你在位置i
时,向前跳nums[i]
步,你将会到达的位置。这个计算显示了从当前位置出发,依据你的最大跳跃能力,能够到达的最远位置。
为什么重要
在解决“跳跃游戏”问题时,我们需要不断地更新在数组中能够到达的最远位置。通过比较coverRange
(当前能覆盖到的最远距离)和i + nums[i]
(从当前位置i
出发能到达的最远距离),我们可以确保coverRange
始终保持在最大值。这是因为,在实际操作中,我们可能不需要每次都跳到最大距离,但了解能跳到的最远距离对于判断是否能到达数组末尾至关重要。
这种方式允许我们在遍历数组的过程中,用最小的步数高效地更新能够到达的最远位置,从而判断是否能够到达数组的最后位置。如果在某一步coverRange
已经大于等于数组的最后一个索引,那么就意味着我们可以到达数组的最后位置。这是一个运用贪心算法思想的典型例子,通过局部最优选择(每一步尽可能跳得远),来达到全局的最优解(到达数组末尾)。
2.2.3、还是看不懂?举个具体例子帮助理解
假设有一个数组nums = [2, 3, 1, 1, 4]
,我们逐步分析代码的执行过程:
- 初始覆盖范围
coverRange = 0
,表示在开始时我们还没有开始移动,因此覆盖范围为0。 - 开始遍历数组,
i = 0
时,nums[0] = 2
,这意味着从位置0,我们最多可以向前跳2步。因此,coverRange
更新为max(0, 0 + 2) = 2
。现在覆盖范围扩展到了索引2的位置。 - 下一步,
i = 1
,nums[1] = 3
,从位置1我们可以跳3步,这意味着我们可以达到更远的位置,即coverRange = max(2, 1 + 3) = 4
。现在覆盖范围扩展到了数组的末尾。 - 由于覆盖范围已经达到或超过了数组的最后一个位置(覆盖范围索引从0开始,所以数组的最后一个位置是
nums.length - 1 = 4
),循环会提前终止,并返回true
,表示我们可以跳到数组的最后位置。
三、跳跃游戏II
45.原题链接
3.1、思路
- 翻译成人话就是计算跳跃到终点的最少步数。
3.2、正确写法1
class Solution {public int jump(int[] nums) {int result = 0;int end = 0;int temp = 0;// 在可以覆盖的范围内遍历,且只有当我们还没有到达数组末尾时才继续。for (int i = 0; i <= end && i < nums.length - 1; i++) {// 计算当前最大可覆盖范围temp = Math.max(i + nums[i], temp);// 遍历到了当前跳跃可以覆盖的最远距离下标,还没有到数组的终点位置,需要继续往下走1步。if (i == end) {end = temp;result++;}}return result;}
}
3.2.1、如何理解这段代码
理解这段代码的关键在于理解end
和temp
两个变量的作用:
end
变量表示当前跳跃可以覆盖的最远距离下标。初始时,由于我们还没有开始跳跃,它被设置为0。temp
变量表示下一次跳跃可以覆盖的最远距离下标。它用于在遍历过程中不断更新我们可以达到的最远距离。
算法执行的流程如下:
- 初始化
result
为0,表示跳跃次数;end
为0,表示当前覆盖的最远距离下标;temp
为0,表示下一步覆盖的最远距离下标。 - 遍历数组元素(注意,循环的条件是
i <= end && end < nums.length - 1
,意味着我们会在可以覆盖的范围内遍历,且只有当我们还没有到达数组末尾时才继续)。 - 在每一次遍历中,我们更新
temp
为i + nums[i]
和temp
中的较大值,这表示如果从当前位置i
跳跃,我们能到达的最远位置。 - 当遍历到当前覆盖的最远距离
end
时(即i == end
),说明我们需要进行一次跳跃以到达更远的位置。这时,我们将end
更新为temp
(因为temp
是我们在当前覆盖范围内可以达到的最远距离),并将跳跃次数result
增加1。 - 重复步骤3和4,直到覆盖范围包含数组的最后一个位置,这时跳出循环。
- 返回
result
作为结果,即到达数组末尾所需的最少跳跃次数。
通过这种方式,代码贪心地在每一步跳跃中都尽可能向前跳到最远的位置,从而确保跳跃次数最少。
3.2.2 、如何理解i == end
在这段代码中,i == end
这个条件用于判断是否遍历到了当前跳跃可以覆盖的最远距离下标。这里的end
变量表示在当前跳跃次数下,我们可以到达的最远位置的索引。每当i
(当前遍历到的位置的索引)与end
相等时,意味着我们已经到达了当前跳跃能够覆盖的最远范围的边界。此时有两个关键点需要注意:
-
跳跃次数的增加:当
i
达到了当前的end
时,我们需要进行一次新的跳跃(即增加跳跃次数result
),因为我们已经尽可能地利用了当前跳跃的能力,而要进一步前进,就必须开始一次新的跳跃。 -
更新下一次的最远距离:在每次遍历过程中,我们通过比较
temp
和i + nums[i]
来更新下一次跳跃可以到达的最远距离。而当i == end
时,即我们达到了当前能达到的最远距离,这时我们就需要把end
更新为temp
,因为temp
此时代表了下一次跳跃可以到达的最远距离。
举个具体例子:
假设数组是 nums = [2, 3, 1, 1, 4]
。
初始时,我们在位置0,end = 0
,表示当前跳跃能达到的最远距离(这里是指下标,下标从0开始),temp = 0
表示下一步跳跃可以到达的最远距离,result = 0
表示跳跃次数。
-
第一轮循环: 初始
i = 0
,end = 0
。- 当
i = 0
时,nums[0] = 2
,表示从位置0出发最多可以向前跳2步。所以,temp = max(temp, i + nums[i]) = max(0, 0 + 2) = 2
。这意味着在当前这一跳中,我们可以到达的最远是下标为2的位置。 - 在这轮循环的结束时(
i == end
),意味着我们已经达到了当前跳跃能到达的最远位置。此时我们将end
更新为temp
的值(也就是2),表示下一次跳跃的最远范围。同时,result++
表示完成了一次跳跃。
- 当
-
第二轮循环: 现在,
i
会从1继续增加,直到i = end
(此时end = 2
)。- 当
i = 1
时,nums[1] = 3
,这意味着从位置1出发最多可以向前跳3步,因此temp
更新为max(2, 1 + 3) = 4
。现在temp
表示我们从当前所有检查的位置出发能到达的最远位置是下标4。 - 当
i = 2
时,即i
达到了当前end
的值,这标志着需要进行一次新的跳跃。因为temp
此时为4,表示下一次跳跃能达到的最远位置已经是数组的最后了。我们将end
更新为temp
,同时result++
。
- 当
通过上面的过程,我们完成了两次跳跃就到达了数组的最后一个位置,因此函数返回result = 2
。
这个例子中,“当i
增加到end
的值时,说明我们已经达到了当前跳跃能够到达的最远位置”这句话的含义是:在每次的跳跃过程中,当我们遍历到end
所标记的位置时,意味着我们已经利用当前这一跳的能力达到了最大范围,此时需要基于temp
(记录了从起点到当前点的所有可能的最远跳跃点)的值来开始下一跳,并且这一跳的结束点(即下一次的end
)就是当前的temp
。
总结
1.感想
- 跳跃游戏II这鬼题目都看不懂,还是看卡尔的翻译一下才看得懂题目,题意不说人话的,翻译成人话就是计算跳跃到终点的最少步数。
- 跳跃游戏II写不出来,抄的题解。。
2.思维导图
本文思路引用自代码随想录,感谢代码随想录作者。