LeetCode 55. 跳跃游戏
题目描述
给定一个非负整数数组 nums,你最初位于数组的第一个下标(索引为0)。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是判断你是否能够到达最后一个下标。
示例 1:
输入: nums = [2,3,1,1,4]
输出: true
解释: 从索引0跳1步到索引1,再从索引1跳3步到最后一个索引。
示例 2:
输入: nums = [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引3的位置,但该位置的最大跳跃长度是0,所以你永远无法到达最后一个索引。
解题过程
这个问题不要求你找出具体的跳跃路径,只要求判断可行性。因此,我们可以采用一种高效的贪心策略来解决。
-
核心思路
我们不必模拟每一次跳跃,而是时刻维护一个“当前能够到达的最远位置”,我们称它为max_reach。只要这个最远位置能够覆盖到数组的最后一个索引,就说明我们可以到达终点。 -
关键观察
对于数组中的任何一个位置i,我们能否到达它,取决于在它之前的所有位置中,至少有一个位置j满足j + nums[j] >= i(即从j可以一步跳到i)。
我们的贪心算法就是基于这个观察的优化:我们不需要检查每一个j,只需要确保我们当前能够到达的最远位置max_reach大于等于i即可。 -
算法步骤
让我们一步步遍历数组nums的每个索引i(从0开始)。-
步骤零:初始化
定义一个变量max_reach,表示从起点(索引0)出发,当前能够到达的最远索引。初始时,我们在起点,所以max_reach = 0 + nums[0]。 -
步骤一:遍历数组
我们从索引i = 1开始遍历,直到倒数第二个元素(即i < nums.length - 1)。因为我们的目标是到达最后一个索引,如果已经能到达最后一个索引,循环就可以提前结束。 -
步骤二:可行性检查(核心)
在遍历到每个位置i时,我们首先要做一个关键判断:
“我当前能走到这个位置i吗?”
判断依据就是:如果当前的最大可达距离max_reach小于当前位置i,那就意味着我们根本无法到达i,更不用说从i继续往后跳了。 此时,可以直接得出结论:无法到达终点,返回false。 -
步骤三:更新最远距离
如果我们可以到达当前位置i(即max_reach >= i),那么我们就从这个新的位置i尝试更新“全局最远可达距离”。
计算从i位置能跳到的最远位置:i + nums[i]。
然后比较max_reach和i + nums[i],将max_reach更新为两者的最大值。
max_reach = max(max_reach, i + nums[i]) -
步骤四:最终判断
在遍历过程中,如果max_reach已经大于或等于最后一个索引(nums.length - 1),我们可以提前结束循环,返回true。
如果遍历顺利结束,我们最后再检查一次max_reach >= nums.length - 1,并返回结果。
-
-
用示例进行推演
我们以示例1nums = [2, 3, 1, 1, 4]为例:i = 0: 初始化max_reach = 0 + nums[0] = 2。i = 1: 判断max_reach (2) >= i (1)?是,可以到达位置1。然后更新max_reach = max(2, 1+3) = max(2,4) = 4。此时max_reach(4)已经 >= 最后一个索引(4),所以提前返回true。
再以示例2
nums = [3, 2, 1, 0, 4]为例:i = 0: 初始化max_reach = 0 + 3 = 3。i = 1:max_reach(3) >= 1,可到达。更新max_reach = max(3, 1+2) = 3。i = 2:max_reach(3) >= 2,可到达。更新max_reach = max(3, 2+1) = 3。i = 3:max_reach(3) >= 3,可到达。更新max_reach = max(3, 3+0) = 3。i = 4: 现在要判断能否到达位置4。我们在遍历到i=4之前,先做步骤二的检查:当前max_reach是3,而i是4,3 < 4。这意味着我们根本无法走到索引4这个位置。因此,算法在尝试进入i=4的循环体之前就会停止(或者在循环判断时发现i=4已经大于max_reach而提前退出),并返回false。
总结
这个算法的妙处在于其贪心性质:我们只关心最远能到哪里,而不关心具体路径。时间复杂度是 O(N),因为只需要遍历一次数组;空间复杂度是 O(1),只使用了常数级别的额外变量。