问题简介
题目描述
给定一个非负整数数组 nums,你最初位于数组的第一个下标。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例说明
✅ 示例 1:
text
输入: nums = [2,3,1,1,4]
输出: true
解释: 从下标 0 跳 1 步到下标 1,然后跳 3 步到达最后一个下标。
✅ 示例 2:
text
输入: nums = [3,2,1,0,4]
输出: false
解释: 无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0,所以永远不可能到达最后一个下标。
解题思路
📌 核心思想 :我们不需要知道具体怎么跳,只需要判断能否到达终点。因此,可以维护一个变量表示当前能到达的最远位置。
✅ 方法一:贪心算法(推荐)
步骤如下:
- 初始化
maxReach = 0,表示当前能到达的最远下标。 - 遍历数组(只需遍历到倒数第二个元素,因为最后一步不需要再跳):
- 如果当前下标
i > maxReach,说明无法到达当前位置,直接返回false。 - 否则,更新
maxReach = max(maxReach, i + nums[i])。
- 如果当前下标
- 遍历结束后,若
maxReach >= n - 1,则可以到达终点。
💡 为什么有效?
因为我们始终记录"能到达的最远位置",只要这个位置 ≥ 最后一个下标,就说明可达。
❌ 方法二:动态规划(不推荐,仅作对比)
- 定义
dp[i]表示是否能到达下标i。 - 初始
dp[0] = true。 - 对每个
i,若dp[i] == true,则将i+1到i+nums[i]的位置都设为true。 - 最终返回
dp[n-1]。
⚠️ 缺点:时间复杂度 O(n²),在 LeetCode 上会超时或效率低。
✅ 方法三:反向遍历(另一种贪心)
- 从后往前找,维护一个
lastPos = n - 1(目标位置)。 - 若
i + nums[i] >= lastPos,说明从i可以跳到lastPos,更新lastPos = i。 - 最终若
lastPos == 0,说明可以从起点跳到终点。
💡 这种方法逻辑清晰,但实际效率略低于正向贪心(因需完整遍历)。
代码实现
java
// Java 实现 - 贪心(正向)
class Solution {
public boolean canJump(int[] nums) {
int maxReach = 0;
int n = nums.length;
for (int i = 0; i < n; i++) {
if (i > maxReach) {
return false;
}
maxReach = Math.max(maxReach, i + nums[i]);
// 提前终止优化
if (maxReach >= n - 1) {
return true;
}
}
return true;
}
}
go
// Go 实现 - 贪心(正向)
func canJump(nums []int) bool {
maxReach := 0
n := len(nums)
for i := 0; i < n; i++ {
if i > maxReach {
return false
}
if i + nums[i] > maxReach {
maxReach = i + nums[i]
}
// 提前终止
if maxReach >= n - 1 {
return true
}
}
return true
}
💡 提示 :以上代码均采用正向贪心,并加入提前终止优化,效率最高。
示例演示
以 nums = [2,3,1,1,4] 为例:
| i | nums[i] | maxReach(更新前) | 是否 i > maxReach? | 更新后 maxReach |
|---|---|---|---|---|
| 0 | 2 | 0 | 否 | max(0, 0+2)=2 |
| 1 | 3 | 2 | 否 | max(2, 1+3)=4 |
| 2 | 1 | 4 | 否 | 4(不变) |
| ... | ... | ... | ... | ... |
✅ 在 i=1 时,maxReach = 4 >= 4(n-1),提前返回 true。
答案有效性证明
📌 贪心选择性质 :
在每一步,我们都选择"能跳得最远"的策略,这不会导致错过可行解。因为如果存在一条路径能到达终点,那么必然存在某个时刻,我们的 maxReach 能覆盖终点。
📌 最优子结构 :
能否到达位置 i,只依赖于前面所有位置中能跳到 i 的最大能力。因此局部最优(最远可达)可推出全局最优(能否到终点)。
✅ 因此,贪心算法在此问题中是正确且完备的。
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 贪心(正向) | O(n) | O(1) | 一次遍历,常数空间 |
| 动态规划 | O(n²) | O(n) | 每个位置可能更新多个状态 |
| 贪心(反向) | O(n) | O(1) | 一次反向遍历 |
✅ 推荐使用正向贪心,简洁高效。
问题总结
- 🔑 关键洞察:无需关心具体跳跃路径,只需关注"最远能到哪"。
- 🧠 思维转变:从"如何跳" → "能不能到",简化问题。
- ⚡ 技巧 :提前终止(一旦
maxReach >= n-1就返回)可提升性能。 - 📌 适用场景:此类"可达性"问题常可用贪心解决,尤其是当"局部最优能保证全局最优"时。
💡 延伸思考 :本题变种 LeetCode 45. 跳跃游戏 II 要求最少跳跃次数,同样可用贪心解决,但需记录"跳跃次数"和"当前边界"。
github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions