今日任务:
1)122.买卖股票的最佳时机II
2)55. 跳跃游戏
3)45.跳跃游戏II
122.买卖股票的最佳时机II
题目链接:122. 买卖股票的最佳时机 II - 力扣(LeetCode)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
文章讲解:代码随想录 (programmercarl.com)
视频讲解:贪心算法也能解决股票问题!LeetCode:122.买卖股票最佳时机II哔哩哔哩bilibili
思路:
当我们观察股票价格的变化时,我们可以发现以下规律:
- 如果股票价格连续上涨,那么我们可以考虑在价格开始上涨的一天买入,在价格达到峰值的一天卖出,这样可以获得最大利润。
- 如果股票价格连续下跌或持平,那么我们不进行交易,因为这样不会带来利润。
具体步骤
- 遍历股票价格数组
prices
。- 维护一个栈
have
,用于记录当前持有的股票价格。- 初始化利润
res
为 0。- 对于每一天的股票价格:
- 如果当前栈为空,说明还没有持有股票,则将当前股票价格入栈。
- 否则,如果当前股票价格高于前一天的价格,说明可以在前一天买入,当天卖出,获取利润。
- 计算利润,并累加到
res
中。- 弹出栈顶元素,表示卖出持有的股票。
- 如果当前股票价格低于前一天的价格,说明前一天的股票价格高于当前价格,因此应该考虑在前一天卖出股票,并更新栈
have
。
- 弹出栈顶元素,表示卖出持有的股票。
- 将当前股票价格入栈,表示持有当前的股票。
- 遍历结束后,返回累计的利润
res
。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 初始化一个栈,用于记录当前持有的股票价格
have = []
# 初始化利润为 0
res = 0
# 遍历股票价格数组
for i in range(len(prices)):
# 如果当前栈为空,说明还没有持有股票,则将当前股票价格入栈
if len(have) == 0:
have.append(prices[i])
else:
# 如果当前股票价格高于前一天的价格,说明可以在前一天买入,当天卖出,获取利润
if prices[i] > prices[i - 1]:
# 计算利润,并累加到 res 中
res += prices[i] - have.pop()
# 如果当前股票价格低于前一天的价格,说明前一天的股票价格高于当前价格,应该在前一天卖出股票,并更新栈 have
elif prices[i] < prices[i - 1]:
# 弹出栈顶元素,表示卖出持有的股票
have.pop()
# 将当前股票价格入栈,表示持有当前的股票
have.append(prices[i])
# 返回累计的利润
return res
思路二
基于刚才的规律,我们可以设计一种贪心的策略:只要当天的价格比前一天的价格高,我们就在前一天买入,当天卖出,这样可以获得最大利润。因为连续的价格上涨可以看作多次买入和卖出的组合,所以这种策略可以得到最大利润。
基于这个思路,我们遍历股票价格数组,比较当前价格与前一天的价格,如果当前价格大于前一天的价格,则可以在前一天买入,第二天卖出,获取利润。最后将所有正的利润累加,即可得到最大利润。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
res = 0
for i in range(1, len(prices)):
res += max(prices[i] - prices[i - 1], 0)
return res
55. 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
文章讲解:代码随想录 (programmercarl.com)
视频讲解:贪心算法,怎么跳跃不重要,关键在覆盖范围 | LeetCode:55.跳跃游戏哔哩哔哩bilibili
思路:
- 从数组的倒数第二个位置开始向前遍历数组。
- 对于每个位置
i
,检查是否可以跳跃到当前位置cur
,即nums[i] >= cur - i
。- 如果当前位置可以跳跃到,更新
cur
为i
,表示当前位置为新的可到达位置。- 最后检查
cur
是否等于 0,如果等于 0,表示可以到达最后一个位置,返回 True;否则返回 False。
class Solution:
def canJump2(self, nums: List[int]) -> bool:
cur = len(nums) - 1 # 初始化当前能够到达的最远位置为数组最后一个位置
for i in range(len(nums) - 2, -1, -1): # 从倒数第二个位置向前遍历
if nums[i] >= cur - i: # 如果当前位置能够跳到当前所能到达的最远位置
cur = i # 更新当前能够到达的最远位置为当前位置
return cur == 0 # 如果最终能够到达的最远位置是数组的第一个位置,则返回 True,否则返回 False
思路二:
当解决这个问题时,我们希望尽可能地跳跃,以达到最远的位置。我们可以使用贪心算法来解决这个问题。下面是算法的详细步骤:
- 我们从数组的第一个位置开始,记录当前能够到达的最远位置为
max_reach
,初始值为 0。- 遍历数组,对于当前位置
i
:
- 如果当前位置
i
大于max_reach
,说明无法到达最后一个位置,因此返回 False。- 否则,更新
max_reach
为当前位置i
可以达到的最远位置,即max_reach = max(max_reach, i + nums[i])
。- 如果遍历结束时,最远位置
max_reach
大于等于最后一个位置(即max_reach >= len(nums) - 1
),则说明可以到达最后一个位置,返回 True,否则返回 False。这样,我们通过贪心算法来判断是否能够到达最后一个位置。算法的时间复杂度为 O(n),其中 n 是数组的长度。
class Soultion:
def canJump3(self, nums: List[int]) -> bool:
# 记录当前能够到达的最远位置
max_reach = 0
# 遍历数组
for i in range(len(nums)):
# 如果当前位置大于最远位置,说明max_reach无法覆盖位置i,返回 False
if i > max_reach:
return False
# 更新最远位置
max_reach = max(max_reach, i + nums[i])
# 如果遍历结束时,最远位置大于等于最后一个位置,返回 True,否则返回 False
return max_reach >= len(nums) - 1
45.跳跃游戏II
题目链接:45. 跳跃游戏 II - 力扣(LeetCode)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
文章讲解:代码随想录 (programmercarl.com)
视频讲解:贪心算法,最少跳几步还得看覆盖范围 | LeetCode: 45.跳跃游戏II_哔哩哔哩_bilibili
思路:
这个问题可以使用贪心算法来解决。我们可以维护两个变量
max_reach
和steps
,其中max_reach
表示当前能够到达的最远位置,而steps
表示跳跃的次数。首先,我们初始化
max_reach
和steps
都为 0,然后我们遍历数组中的每个位置i
,在遍历过程中更新max_reach
为max(max_reach, i + nums[i])
,这表示当前位置能够到达的最远位置。当我们遍历到i
时,如果i == max_reach
,说明我们已经到达了当前能够到达的最远位置,这时我们就需要进行一次跳跃,将max_reach
更新为i + nums[i]
,同时steps
自增 1。我们遍历完整个数组后,steps
就是我们所需要的最少跳跃次数。
从下标0开始,第一次跳跃最远为2,也就是区间[0,2],此时我们记录end = 2,2不是终点,所以肯定是要跳的,就是看在这个区间中,选择跳到哪,下一次跳跃区间更远。假如第一次跳跃,我们跳到区间中的索引index = 1,那么下一次跳跃的最远距离就是index + nums[index] = 4。假如第一次跳跃,我们跳到区间中的索引index = 2,那么下一次跳跃的最远距离就是index + nums[index] = 3,所以,我们应该选择index=1,此时最远距离为4,我们记录max_reach = 4,遍历的区间终点end=4。在这个遍历第二次跳跃区间的过程中,我们遇到比4大的则更新max_reach,直到遍历完到区间边界end,还没有到终点,则进行二跳。以此类推,直到遍历区间包含终点,则结束。
class Solution:
def jump(self, nums: List[int]) -> int:
max_reach = 0 # 当前能够到达的最远位置
steps = 0 # 跳跃的次数
end = 0 # 当前步数内能够到达的最远位置
for i in range(len(nums) - 1):
max_reach = max(max_reach, i + nums[i]) # 记录当前位置能到达最远位置区间中,下一个最远距离。(即,此次跳跃中,能实现的最大距离)
if i == end: # 如果遍历到当前步数内能够到达的最远位置
end = max_reach # 更新当前步数内能够到达的最远位置为当前能够到达的最远位置
steps += 1 # 进行一次跳跃
return steps
感想:
代码好写,方法不好想,可以先按数学逻辑写出一版基础的,再去优化代码,熟练解题技巧