给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
贪心思路比较好理解,我们并不需要关注某一个格子到底需要跳几格,只要每次都找到当前下标可以达到最远距离,判断这个最远距离是不是能达到数组的最后就行了。
我偷了个图,看了就明白了。
举例:
若所给数组为nums = [2,3,1,4,6,7] 遍历到2的时候,最远距离可以到1,而遍历到3的时候,最远距离可以到 3所在的下标 +当前下标可以跳跃的最远举例,即
1+nums[1] = 4
另一种情况就是 nums = [6,2,1,4,6,7] ,此时遍历到2 的时候,最远距离可以到 1+2 = 3,但是前一个数是6,所以最远距离并没有变。
由此推导代码: 最远距离 设为 max_index 。
则:max_index = max(i+nums[i],max_index)
其实和贪心思路差不多。
老规矩 五步走:
1.确定dp数组含义:
dp[i] 表示从下标i起跳可以调到的最远距离。
2.状态转移公式:
跟贪心思路一样,两种情况:有可能当前nums[i] 所表示的距离是新的最大距离,也有可能之前的值是最大距离,比如 [1,4] 遍历到4就是新的最大距离,而[6,1],即使遍历到了1,最大距离也在是的6。
在看一下dp含义,dp[i] 表示下标i可以到达的最远距离。
所以,如果是[6,1]这种情况,当下标遍历到1,此时最远距离是 在上一时刻也就是遍历到6的那个时刻的最远距离减一,因为从6走到了1嘛。
故:
dp[i] = max(dp[i-1]-1,nums[i])
3.初始化dp数组:
dp[i]依赖于dp[i-1],所以第一个值需要初始化。
回顾dp含义 dp[i] 表示下标i可以到达的最远距离。
显然:dp[0] = nums[0]
4.遍历顺序:
没得说,从前往后。
5.返回值:
这里其实有一点细节,就是如果dp[i-1] = 0 那么就意味着从i-1下标处可以移动的最远距离为0,也就是无法向前移动了,自然也无法达到i,更无法达到数组末尾了。此时直接返回False即可。
遍历到最后return True
根据dp公式 : dp[i] = max(dp[i-1]-1,nums[i])
当前值只依赖于前一个值和已给的nums数组,所以只需要维护两个值就行了,可以用状态压缩优化一下代码。
不看公式也比较好想,比如[2,6,1] 实际上6已经考虑了前面的2了,到6这里最大距离更新为6,所以1也只需要考虑自己和前一个的最大距离,[10,6,1]也是这样,遍历到6的时候,6考虑了前面的10,最大距离没更新。所以一直都是每一个状态只需要考虑自己和前一个状态。
贪心:
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_index = 0
for i in range(len(nums)):
# 之前可达最远处和最新的可达最远处
max_index = max(i + nums[i],max_index)
if max_index >= len(nums)-1:
return True
if max_index-1 < i :
return False
动态规划:
class Solution:
def canJump(self, nums: List[int]) -> bool:
# dp[i] 表示在下标i处可以达到的最远距离。
dp = [0] * len(nums)
dp[0] = nums[0]
for i in range(1,len(nums)):
dp[i] = max(dp[i-1]-1,nums[i])
if dp[i-1] == 0:
return False
return True