题干
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
示例 1:
输入: nums = [2,3,1,1,4]
输出: true
解释: 可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入: nums = [3,2,1,0,4]
输出: false
解释: 无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
题解
第一次做这个题目时,我的想法是走到每一个点,都有两种方式可能可以走到终点,一种是自己就可以走到终点,即nums[i] + i >= len(nums) - 1,还有一种是自己不能走到终点,但是可以先跳到别的点,再靠其他的点走到终点,即nums[i] + i < len(nums) - 1,但是存在k使得i + nums[i] + nums[k] >= len(nums) - 1 。根据这个思路想到以下两个办法
递归
func canJumpFrom(start int, nums []int) bool {
step := nums[start]
if start == len(nums)-1 {
return true
}
for i := 1; i <= step; i++ {
flag := canJumpFrom(start+i, nums)
if flag {
return true
}
}
return false
}
func canJump(nums []int) bool {
return canJumpFrom(0, nums)
}
动态规划
func canJump(nums []int) bool {
n := len(nums)
dp := make([]bool, n)
dp[n-1] = true
for i := n - 1; i >= 0; i-- {
if nums[i]+i >= n-1 {
dp[i] = true
} else {
dp[i] = false
for step := 1; step <= nums[i]; step++ {
if dp[i+step] {
dp[i] = true
break
}
}
}
}
return dp[0]
}
这两种方法是可行的,但是存在多余的遍历操作,在上述两个方法中,在走到每个点的时候,如果这个点不能到达终点,都会遍历这个点所有可达的点,检查这些点能不能到达终点。实际上可以利用贪心的思想,维护一个最大可达位置,当遍历到一个点,发现它不可以直接达到终点时,不需要立即遍历其所有可达的点,只需要记录下其最大的可达位置,在继续往后遍历的过程中,如果某个点在当前最大可达位置内,就说明这个点可以由之前的点达到,之后更新这个最大的可达位置,如果遍历完成后,如果最大可达位置 >= 终点,就返回true。否则就返回false。类似的,还可以反向遍历数组,反向遍历过程中维护一个能够达到终点的点的最近位置。
正向贪心
func canJump(nums []int) bool {
var maxLoc int
n := len(nums)
for i := 0; i < n; i++ {
if i <= maxLoc {
maxLoc = max(i+nums[i], maxLoc)
if maxLoc >= n-1 {
return true
}
}
}
return false
}
反向贪心
func canJump(nums []int) bool {
n := len(nums)
step := 1
for i := n - 2; i >= 0; i-- {
if nums[i] >= step {
step = 0
}
step++
}
return step == 1
}