【leetcode-Python】-动态规划&贪心-45. Jump Game II

题目链接

https://leetcode.com/problems/jump-game-ii/

题目描述

给定非负整数数组nums,你最初位于数组的第一个下标。数组中的每个元素表示你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。(假设你总是可以到达数组的最后一个位置。)

示例

输入:[2,3,1,1,4]

输出:2

跳到最后一个位置的最小跳跃次数为2。从下标0跳到下标为1的位置,然后从下标为1的位置跳到最后一个位置。

解题思路一

动态规划算法有一部分适用的题目为“求最值”,比如:

最长回文子序列:【leetcode-Python】- Dynamic programming-516. Longest Palindromic Subsequence

编辑距离:【leetcode-Python】-Dynamic Programming-72. Edit Distance

换零钱:【leetcode-Python】-Dynamic Programming-322. Coin Change

买卖股票系列问题:【leetcode-Python】-Dynamic Programming -121. Best Time to Buy and Sell Stock【leetcode-Python】-Dynamic Programming -122. Best Time to Buy and Sell Stock II【leetcode-Python】-Dynamic Programming -123. Best Time to Buy and Sell Stock III

最长公共子串:【leetcode-Python】-Dynamic Programming-718. Maximum Length of Repeated Subarray

最长公共子序列:【leetcode-Python】-Dynamic Programming-1143. Longest Common Subsequence(LCS)

最长上升子序列:【leetcode-Python】-Dynamic Programming-300. Longest Increasing Subsequence(LIS)

和绝对值最大子序列:【leetcode-Python】-Dynamic Programming-1749. Maximum Absolute Sum of Any Subarray

最大和子串:【leetcode-Python】-Dynamic Programming -53. Maximum Subarray(最大和连续子数组问题)

这道求最少跳跃次数的问题,也可以用动态规划来做。动态规划的核心也是穷举,但是由于重叠子问题以及最优子结构的存在,我们可以记住子问题的最值,并依据此得到原问题的最值。由于题目要求是从索引0跳到数组最后一个位置的最少跳跃次数,因此我们可以把状态设置为当前所在的索引i,由dp[i]表示从索引i跳到数组最后一个位置的最少跳跃次数(状态可以从题目要求中来猜)。那么在索引i可选的选择为“当前跳几步”。

确定好状态和选择后,我们可以确定状态转移数组。在索引i的位置能够跳跃的最大长度为nums[i],那么从索引i可以跳到i+1,...,i+nums[i]。如果知道从索引i+1,...,i+nums[i]跳到数组最后一个位置的最少跳跃次数,dp[i]应该取这些这些跳跃次数中的最小值再加1。因此状态转移数组为dp[i] = min(dp[i+1],...,dp[i+nums[i])+1(这里需要确保索引不越界)。base case为当前就在数组的最后一格,此时不需要跳跃,即有dp[len(nums)-1] = 0。

Python实现

class Solution:
    def jump(self, nums: List[int]) -> int:
        dp = [float('inf') for _ in range(len(nums))] #初始化为较大的数
        dp[-1] = 0
        for i in range(len(nums)-2,-1,-1):#反向更新
            for step in range(1,nums[i]+1):#注意step可以取到nums[i],因此range这里右边界应该写为nums[i]+1
                if(i+step<len(nums)):
                    dp[i] = min(dp[i],dp[i+step]+1)
                    print(dp[i])
        return dp[0]
                
            

时间复杂度与空间复杂度

时间复杂度为O(N),空间复杂度为O(N)。

解题思路二

一部分动态规划的题目可以由贪心策略来解,这道题也可以由贪心来做。每次选择只选择“当前最优“、”最有潜力的选择“。如何判断是否是当前最优呢?就看做出这个选择后,最远能够到达的位置。

比如对于[2,3,1,1,4],在索引为0的位置能够跳到索引为1的位置和索引为2的位置。在索引为1的位置能够进一步到达的最远位置为4,在索引为2的位置能够进一步到达的最远位置为3,显然如果跳到索引为1的位置,下一步就能跳得更远(跳到4)。那么做出”当前最优“的选择,即为选择”能再进一步跳得最远“的位置。

在具体实现中,我们维护在当前位置i能够跳到的最远位置,并记为边界cur_end。用cur_farthest维护在[i,...,cur_end]范围里的位置跳跃能够到达的最远位置,即在所有选择[i,...,cur_end]中能够跳到的最远位置。从左到右遍历数组,到达边界cur_end时(i==curEnd),表示触发一次跳跃,跳跃到“当前最优的选择”(即[i,...,cur_end]范围里能进一步跳得最远的那个下标),然后我们更新cur_end为cur_farthest,相当于做出了选择。

比如对于[2,3,1,1],初始化cur_end为0,cur_farthest也为0,当前跳跃次数jumps为0。(其实更准确一点来讲,jumps维护的是起跳次数

从索引为0的位置开始考虑,cur_farthest取2,表示在0这个位置跳跃能够到达的最远距离。由于此时cur_end==i==0(初始化可选位置只有索引0),因此触发一次跳跃,增加起跳次数jumps,并将curEnd更新为cur_farthest.

继续遍历,在索引为1的位置能够到达的最远距离为4,因此更新farthest为4,在索引为2的位置能够到达的最远距离为3,farthest仍为4。此时有i==cur_end,即已经到达边界了(从0开始跳的所有可选跳跃长度都考虑完了),因此可以再触发一次跳跃,更新新的边界cur_end为farthest值,即为4(索引为1的位置能够跳到的最远位置)。同时也要对跳跃次数加1,表示从索引为1的位置起跳并继续跳跃

不访问最后一个元素的原因

由于题目给出假设:“一定可以到达最后一个位置”,因此在遍历到最后一个位置之前,边界cur_end一定会大于等于最后一个位置,因此我们不需要访问最后一个元素。同时如果访问最后一个位置,并且边界cur_end也等于最后一个位置,那么会再增加一次不必要的跳跃

Python实现

class Solution:
    def jump(self, nums: List[int]) -> int:
        cur_end,cur_farthest = 0,0
        jumps = 0
        for i in range(0,len(nums)-1):
            cur_farthest = max(cur_farthest,nums[i]+i)
            if(i == cur_end):#到达右边界
                jumps += 1#起跳,增加跳跃次数
                cur_end = cur_farthest
        return jumps
        

时间复杂度与空间复杂度

时间复杂度为O(N),空间复杂度为O(1)。

参考

https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/tan-xin-lei-xing-wen-ti/tiao-yue-you-xi

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值