给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
解题思路
这个问题是之前Leetcode 55:跳跃游戏(最详细的解法!!!)的提升。我们先看看这个问题能不能通过动态规划解决,很简单,只是对之前的问题稍加修改,我们这里要考虑的问题是到index
的最小步数,也就是之前的[0:index-1]
中向前一步能到达index
的最小值。
我们在初始化的时候将mem
全部初始化为inf
,而mem[0]=0
即可。
class Solution:
def jump(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums_len = len(nums)
mem = [float('inf')]*nums_len
mem[0] = 0
for i in range(1,nums_len):
for j in range(i):
if nums[j] + j >= i:
mem[i] = min(mem[j]+1, mem[i])
break
return mem[-1]
但是这样写法超时了,原因和之前问题一样,一定是有些可以剪枝的思路没有考虑到。我们如果按照之前思路,将这个问题反过来考虑的话,也就是
class Solution:
def jump(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums_len = len(nums)
mem = [float('inf')]*nums_len
mem[0] = 0
for i in range(1,nums_len):
for j in range(i, -1, -1):
if mem[j] != float('inf') and nums[j] + j >= i:
mem[i] = min(mem[j]+1, mem[i])
break
return mem[-1]
但是这样思考对吗?对于上面的例子
2 3 1 1 4
0 1
我们考虑1
,如果用我们上面写的代码的话,这里我们就需要2
步到1
,而实际上只要1
步,原因在于我们break
了,我们没有继续向前查找更小的了。break
去了就可以了吗?实际上这又回到了最原始的代码,没有任何剪枝的考量,显然是不合理的。
这个问题通过贪心算法能否解决呢?我们前面步子尽量迈大一点,后面就有更多的空间了啊?显然这种做法是不可行的,因为我们每次求解的是局部最优解,而对于全局来说就不一定是最优解了。那是不是不能用贪心了?我们可以换一个思路,我们先思考跳一步的话最远可以跳多远,接着思考跳两步的话最远可以跳多远,以此类推直到最远的距离大于等于nums.size()-1
,那么此时的步数自然就是最少的步数。
所以我们先遍历一遍nums
,对于每个index
,我们要判断当前steps[step]
是不是能大于等于nums_len-1
,如果成立,那么我们只要step+1
步即可。如果steps[step]<index
,也就是说当前step
步无法到达index
这个位置,我们就要多跳一步step+1
。对于我们下一步可以跳多远steps[step+1]
这个问题,自然是i in range(index)
这个区间内max(nums[i]+i)
所决定的,这也是之前问题Leetcode 55:跳跃游戏(最详细的解法!!!)中的思想。
class Solution:
def jump(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums_len = len(nums)
steps, step = [nums[0]]*nums_len, 0
for i in range(1, nums_len):
if steps[step] >= nums_len - 1:
return step + 1
if steps[step + 1] < i + nums[i]:
steps[step + 1] = i + nums[i]
if steps[step] <= i:
step += 1
return step
非常简洁,非常酷!!!
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!