给定一个非负整数的数组,每一个元素表示从当前位置开始跳跃一次的最大长度。
你一开始站在第一个索引的位置。
你的目标是用最少的跳跃次数到达最后一个索引位置。输出跳跃次数。
备注:
假设肯定可以跳到最后一个位置。
示例:
Input: [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2.
Jump 1 step from index 0 to 1, then 3 steps to the last index.
方法如下:
比如输入数组为 nums=[1,4,2,3,2,1,1],那么:
1、从最末尾的 nums[6] 开始向左,查询可以跳跃到 nums[6] 的最左边的位置,为 nums[3]=3。
2、从 nums[3] 开始向左,查询可以跳跃到 nums[3] 的最左边的位置,为 nums[1]=4。
3、从 nums[1] 开始向左,查询可以跳跃到 nums[1] 的最左边的位置,为 nums[0]。
4、已经到了起始索引,算法结束,解为 3 步。
虽然是贪婪法,但是可以证明能够达到全局最优解(之一)。
反证法:
假设存在一种步数更少解法B,只需要跳 m-1 步即可。(m 为使用贪婪法的解,贪婪法解法记为A)
那么解法B必定至少存在一次跳跃,使得这次跳跃会至少横跨2个解法A的落脚点,否则不可能会比解法A的步数少。
将这次跳跃的起跳和落脚位置记为 b1 和 b2,包含的2个解法A的落脚点记为 a1 和 a2,有 b1 <= a1 < a2 <= b2,且两个等号不会同时成立。不妨假设第一个等号不成立,即 b1 < a1 < a2 <= b2。
那么既然从 b1 可以直接跳到 b2,则自然可以从 b1 跳到 a2,但是根据解法A的规则,a1 是可以跳跃到 a2 的最左边的位置,这与 b1 < a1 矛盾,因此原假设不成立,命题得证。
时间复杂度为O(n^2)。
上述方法每次更新end,都要重新遍历一遍end之前的所有元素,越靠前的元素,越容易被多次遍历,浪费时间。
可以提前构造一个数组,里面存放着可以跳跃到当前位置的最左边的元素索引。
这样,再按照上述方法的思想,从后向前寻找,每次寻找可以跳到end的最左边索引时,直接从数组中取即可。
而构造这样一个数组,只需要遍历一遍即可:从后向前遍历,对于每个位置,从当前位置开始、到当前位置可以跳跃到的最远距离结束,这之间的所有的位置,在新数组中都更新成当前位置的索引值。由于是从后向前遍历,因此新数组上的同一索引的数只有可能被更小的数替代。
时间复杂度降为 O(n) + O(n) = O(n)。
从前向后跳跃,只需要一次扫描即可,从左向右扫描。
维持4个变量:
原理:
比如输入数组为 nums=[3,2,3,2,1,2,1],那么:
1、从 nums[0] 开始,因为 nums[0]=3,所以可以跳跃到 nums[3],记 farthest=3,end=3,step=1。
2、扫描 nums[1] ~ nums[3],从这3个位置出发可以跳跃到的最远的位置分别为 nums[3]、nums[5]、nums[5],选择最远位置,farthest = 5。更新 end=5,step=2。
3、扫描 nums[4] ~ nums[5],从这2个位置出发可以跳跃到的最远的位置分别为 nums[5]、nums[7],选择最远位置,farthest = 7。更新 end=7,step=3。
4、end已经越界,算法结束,step=3。
由于只扫描一遍,算法复杂度为O(n)。
def jump(nums):
"""
:type nums: List[int]
:rtype: int
从后往前贪婪法。
"""
# 初始化end
end = len(nums)-1
step = 0
# 开始从后向前跳跃
while(end > 0):
left = end
for i in range(end - 1, -1, -1):
if nums[i] >= end - i:
left = min(left, i)
end = left
step += 1
return step
def jump2(nums):
"""
:type nums: List[int]
:rtype: int
改良版。
"""
# 构造数组
l = len(nums)
left_list = [-1] * l
for i in range(l-2, -1, -1):
# 从当前位置向后长度为nums[i]之中的每个位置,都可以由当前位置直接跳跃到
# 由于是从后向前遍历,因此left_list上的同一索引的数只有可能被更小的数替代
left_list[i+1:i+1+nums[i]] = [i] * nums[i]
# 初始化end
end = len(nums)-1
step = 0
# 开始从后向前跳跃
while(end > 0):
end = left_list[end]
step += 1
return step
def jump3(nums):
"""
:type nums: List[int]
:rtype: int
动态规划。
"""
l = len(nums)
if l <= 1:
return 0
end = 0
farthest = 0
step = 0
for i in range(l):
farthest = max(farthest, i + nums[i])
if i == end:
step += 1
end = farthest
if end >= l-1:
break
return step
if '__main__' == __name__:
nums = [2,3,1,1,4]
print(jump3(nums))