代码随想录算法训练营Day32 | 122. 买卖股票的最佳时机II | 55. 跳跃游戏 | 45. 跳跃游戏II

文章目录

  • 122. 买卖股票的最佳时机II
  • 55. 跳跃游戏
    • 动态规划
  • 45. 跳跃游戏II
    • 动态规划

122. 买卖股票的最佳时机II

题目链接 | 解题思路

此题规定了 1)一天只能持有一只股票;2)当天可以同时买卖股票。这样的规定下,最大的收益可以拆分为每个正收益的累计。只要记录每个间隔的收益,并计算正收益即可。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        count = 0
        for i in range(len(prices) - 1):
            if prices[i+1] - prices[i] > 0:
                count += prices[i+1] - prices[i] 
        return count

55. 跳跃游戏

题目链接 | 解题思路

“在每一个节点跳几步”是一个类似回溯的思路,对于本体来说过于复杂,也没有必要。
本题重点是能意识到动态的覆盖范围:如果所有节点跳跃的覆盖范围能够包括最后一个节点,则可以到达;反之不能。意识到这点之后,就不必具体思考跳跃的方式了。

局部最优:在每个节点更新当前的最大覆盖范围
全局最优:得到最终的最大覆盖范围(并判断是否覆盖终点)

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        cover = 0       # include the rightmost element
        i = 0
        while i <= cover:
            cover = max(cover, nums[i] + i)
            if cover >= len(nums) - 1:
                return True
            i += 1
        return False

动态规划

本题的动态规划和贪心算法思路差不多。dp[i] 记录的是当前节点能够到达的最远节点,通过更新 dp 数组判断能否到达最后一个节点。
注意,判断的依据应当是倒数第二个节点:dp[-2] >= len(nums) - 1,但如果能够到达,dp[-1] >= len(nums) - 1 也一定成立,反之则不成立,所以最后写的判断条件是 dp[-1] >= len(nums) - 1

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        dp = [0] * len(nums)
        dp[0] = nums[0]
        for i in range(1, len(nums)):
            if dp[i-1] < i:
                dp[i] = dp[i-1]
            else:
                dp[i] = max(dp[i-1], i + nums[i])
        return dp[-1] >= len(nums) - 1

45. 跳跃游戏II

题目链接 | 解题思路

本题依然要依靠最大覆盖范围来进行贪心,不同的是,上一题中关注的是最大覆盖范围,而本题关注的是增加覆盖范围所用的移动次数。

有一点很关键:对于 i[i+1, i + nums[i]] 内的节点都可以仅通过一次移动到达。例如,nums = [2, 3, 1],从 idx=0 的节点进行一次移动可以到达 idx=1, idx=2 的那些节点。

所以,本题的局部最优是,对于每一次移动,都尽可能增加当前最大覆盖范围。如果当前最大覆盖范围没有包括终点,那就必须进行下一次移动,才有可能到达终点。当最大覆盖范围包括了终点时,就用了最少的移动次数(全局最优)。

在当前步数的覆盖范围 curr_cover 内,不断更新“如果多走一步”的覆盖范围 next_cover。如果发现走到当前覆盖范围的终点,却依然没有遇到全剧终点,那就只能选择多移动一次,在新的覆盖范围内查看能否覆盖全剧终点。

class Solution:
    def jump(self, nums: List[int]) -> int:
        num_jumps = 0
        curr_cover = next_cover = 0
        for i in range(len(nums)):
            next_cover = max(next_cover, i + nums[i])
            if i == curr_cover:
                if curr_cover >= len(nums) - 1:
                    break
                num_jumps += 1
                curr_cover = next_cover
        return num_jumps

动态规划

dp[i] 记录的是到达 idx=i 的最少移动次数。对于 idx=i 的元素来说,我们知道多移动一次就可以到达 idx = [i+1, i+nums[i]] 的元素。所以对于每个元素,都可以更新自己覆盖的节点的 dp。

class Solution:
    def jump(self, nums: List[int]) -> int:
        dp = [float('inf')] * len(nums)
        dp[0] = 0
        for i in range(1, len(nums)):
            for j in range(i, i + nums[i-1]):
                if j < len(nums):
                    dp[j] = min(dp[i-1] + 1, dp[j])
                if j == len(nums) - 1:
                    return dp[j]
        return dp[-1]

以上写法的时间复杂度很高,主要是对于同一个节点 i,dp[i] 可能会被访问很多次。实际只需要记录第一次访问该节点时的步数即可,时间复杂度可以降到 O ( n ) O(n) O(n)

class Solution:
    def jump(self, nums: List[int]) -> int:
        dp = [float('inf')] * len(nums)
        dp[0] = 0
        next_start = 1
        for i in range(1, len(nums)):
            for j in range(next_start, i + nums[i-1]):
                if j < len(nums):
                    dp[j] = min(dp[i-1] + 1, dp[j])
                if j == len(nums) - 1:
                    return dp[j]
            next_start = i + nums[i-1]
        return dp[-1]

你可能感兴趣的:(代码随想录算法训练营一刷,算法)