Jump Game 是 LeetCode Hot 100 里面一道非常经典的贪心题。
这道题表面上看只是一个数组跳跃问题: 给定一个数组,每个位置上的数字表示你从当前位置最多可以跳多远,问你最终能不能到达最后一个位置。
题目本身不算难,但它非常适合用来理解 贪心算法 的一个核心思想:
并不是每一步都真的把路径走出来,而是维护“当前最远能到哪”。
很多人第一次做这题时,会下意识地去想:
-
从当前位置可以跳 1 步、2 步、3 步……
-
这样当然能做,但效率会非常差。 而你给出的这份 Java 代码,用的是这道题最经典、最优雅的一种写法:
贪心维护最远可达位置
这种方法的关键就在于: 我们根本不需要关心“具体怎么跳”,只需要关心“到目前为止,最远能到哪里”。
本文就结合这段代码,系统地讲一下这道题的思路。
题目介绍
给定一个非负整数数组 nums。
数组中的每个元素 nums[i] 表示你在位置 i 时,最多可以向前跳跃的长度。
一开始你站在数组的第一个位置,也就是下标 0。 现在需要判断:你是否能够到达数组的最后一个位置。
例如:
nums = [2,3,1,1,4]
从下标 0 出发,最多能跳 2 步。 一种可行路径是:
-
从
0跳到1 -
再从
1跳到最后一个位置
所以答案是:
true
而如果是:
nums = [3,2,1,0,4]
虽然前面一开始看起来能跳很远,但最终会卡在下标 3,因为那里是 0,无法再继续向前,所以答案是:
false
示例
示例 1
输入:nums = [2,3,1,1,4]
输出:true
因为你可以这样跳:
-
0 -> 1 -> 4
所以可以到达最后一个位置。
示例 2
输入:nums = [3,2,1,0,4]
输出:false
不管怎么跳,都会在下标 3 附近被卡住,无法继续到达最后一个位置。
最直接的想法是什么?
如果一开始按最朴素的思路去想,这道题很容易想到“搜索”:
-
从当前位置出发,尝试所有可能跳到的位置
-
对每个下一步继续递归
-
看有没有哪条路径能到达终点
这个思路本质上就是回溯 / DFS。
但问题在于:
-
同一个位置可能会被重复访问很多次
-
搜索树分支非常多
-
最坏情况下会超时
所以虽然这题可以用递归去理解,但真正高效的做法并不是“试所有跳法”,而是要抓住一个更本质的信息:
当前最远能到达的位置。
核心思路:维护最远可达位置
你这段代码的核心思想其实可以概括成一句话:
遍历数组时,持续维护当前能到达的最远下标 maxPos。
如果当前遍历到的位置 i 已经超过了 maxPos,说明这个位置根本到不了,那后面更不可能到达,直接返回 false。
否则,只要当前位置还能到,我们就尝试用它来更新最远可达范围:
maxPos = max(maxPos, i + nums[i])
其中:
-
i是当前位置 -
nums[i]是从这里最多能跳多远 -
i + nums[i]就是从当前位置出发最远能到达的位置
不断更新后,如果能顺利遍历完整个数组,就说明最后一个位置一定可达。
Java 代码
下面是你给出的代码,我这里保留原始逻辑,并加上注释:
class Solution {
public boolean canJump(int[] nums) {
int maxPos = 0;
for (int i = 0; i < nums.length; i++) {
// 如果当前位置已经超过最远可达范围,说明这里到不了
if (i > maxPos) return false;
// 更新当前最远可达位置
maxPos = Math.max(maxPos, i + nums[i]);
}
return true;
}
}
代码拆解
这段代码非常短,但它把贪心思想体现得非常完整。 下面一步一步来看。
1. maxPos 表示什么?
int maxPos = 0;
maxPos 表示:
在遍历到当前时刻之前,我们最远能够到达的下标。
初始值设为 0,因为一开始我们就站在下标 0 这个位置。
随着遍历进行,如果当前位置可以到达,我们就利用这个位置继续扩展最远可达范围。
所以 maxPos 可以理解成一个“可达边界”。
2. 为什么要遍历整个数组?
for (int i = 0; i < nums.length; i++)
因为我们想逐个检查:
-
当前位置能不能到
-
如果能到,能不能帮我们把最远范围推得更远
这和很多人直觉里“真的跳来跳去”不一样。
这里的遍历并不是在模拟一条具体路径,而是在做一件更高层次的事情:
扫描所有当前可达的位置,并用它们更新全局最远可达位置。
所以看似是普通遍历,实际上是在不断扩大我们的“覆盖范围”。
3. 为什么 i > maxPos 时就可以直接返回 false?
if (i > maxPos) return false;
这是整道题最关键的一句。
因为如果当前下标 i 已经比我们最远能到达的位置还要大,说明:
-
下标
i根本不可达 -
那么它后面的位置自然也更不可能通过它到达
也就是说,只要出现一个“走不到”的位置,后面整个过程就断掉了。
例如:
nums = [3,2,1,0,4]
遍历到下标 4 时,会发现当前的 maxPos 最多只能到 3。 这说明位置 4 根本到不了,那答案就一定是 false。
所以一旦出现:
i > maxPos
直接返回 false 就可以了。
4. 为什么更新公式是 max(maxPos, i + nums[i])?
maxPos = Math.max(maxPos, i + nums[i]);
如果当前位置 i 是可达的,那么从这里出发,最远可以跳到:
i + nums[i]
但我们原本已经有一个历史最远可达位置 maxPos。
所以新的最远可达位置,应该取两者最大值:
max(maxPos, i + nums[i])
这一步就是贪心策略的核心:
每到一个能到达的位置,就尽可能更新全局最远范围。
我们并不关心具体是从哪个点跳过去的,只关心“目前能覆盖到哪里”。
手动模拟一遍:可达情况
我们用经典样例来走一遍:
nums = [2,3,1,1,4]
初始:
maxPos = 0
下标 0
-
i = 0 -
i <= maxPos,说明当前位置可达 -
从这里最远能到:
0 + 2 = 2
更新后:
maxPos = 2
下标 1
-
i = 1 -
1 <= 2,说明可达 -
从这里最远能到:
1 + 3 = 4
更新后:
maxPos = 4
此时其实已经能覆盖到最后一个位置了。
下标 2
-
2 <= 4 -
可达
-
从这里最远能到:
2 + 1 = 3
更新后 maxPos 仍然是 4。
下标 3
-
3 <= 4 -
可达
-
最远能到:
3 + 1 = 4
maxPos 不变。
下标 4
-
4 <= 4 -
可达
遍历结束,返回:
true
手动模拟一遍:不可达情况
再看这个例子:
nums = [3,2,1,0,4]
初始:
maxPos = 0
下标 0
-
可达
-
最远能到:
0 + 3 = 3
更新:
maxPos = 3
下标 1
-
1 <= 3 -
可达
-
最远能到:
1 + 2 = 3
maxPos 仍然是 3。
下标 2
-
2 <= 3 -
可达
-
最远能到:
2 + 1 = 3
maxPos 不变。
下标 3
-
3 <= 3 -
可达
-
最远能到:
3 + 0 = 3
还是不能往前推进。
下标 4
这时:
i = 4
maxPos = 3
因为:
4 > 3
说明位置 4 根本到不了,直接返回:
false
为什么这个方法是正确的?
因为这道题真正关心的并不是“路径本身”,而是“可达范围”。
假设我们已经知道:
-
从前面某些位置出发,最远能到达
maxPos
那么对于当前下标 i:
如果 i > maxPos
说明当前位置不可达。 而一个不可达的位置,不可能帮助我们继续往前推进,所以后面也不可能成功。
如果 i <= maxPos
说明当前位置可达。 那么它就有资格用自己的跳跃能力来扩大可达范围:
i + nums[i]
于是只要一路扫描并不断更新最远边界,就可以正确判断是否能到终点。
这正是贪心策略的本质:
每一步都维护当前最重要的信息——最远可达位置。
复杂度分析
时间复杂度
只遍历了一次数组,每个位置只处理一次,所以时间复杂度是:
O(n)
空间复杂度
只使用了一个额外变量 maxPos,所以空间复杂度是:
O(1)
这也是这道题贪心解法最大的优势: 既快,又省空间。
这道题真正想考什么?
这道题表面上是在“跳格子”,但本质上考察的是贪心思维。
第一,能不能抓住真正重要的信息
很多人一开始会被“具体跳法”吸引,去想到底怎么跳。 但这题真正重要的其实不是路径,而是:
当前最远能到哪里。
一旦抓住这个核心,问题就会一下子变得简单很多。
第二,能不能用局部信息推全局结果
每次更新 maxPos,都是在做一个局部最优选择:
-
当前能到的点里,谁能让我走得更远?
而通过不断维护这个局部最优,最终就能判断整个数组是否可达终点。
这就是典型的贪心思想。
和“跳跃游戏 II”有什么区别?
这道题和后面的“跳跃游戏 II”很像,但目标不同。
跳跃游戏(本题)
只问:
能不能到达最后一个位置
所以只需要维护最远可达范围,返回布尔值。
跳跃游戏 II
问的是:
最少跳几次能到最后一个位置
那时候除了可达性,还要进一步考虑“分层推进”和“步数统计”。
所以这道题其实是“跳跃游戏 II”的基础版。
总结
这道题的核心思想可以概括成一句话:
遍历数组时,持续维护当前最远可达位置;如果某个位置已经超过这个范围,说明无法到达终点。
具体做法就是:
-
用
maxPos表示当前最远可达下标 -
遍历数组中的每个位置
-
如果当前位置
i > maxPos,说明当前位置不可达,直接返回false -
否则更新:
maxPos = max(maxPos, i + nums[i])
-
如果顺利遍历完数组,说明最后一个位置可达,返回
true
这样就能在:
O(n)
的时间复杂度和:
O(1)
的空间复杂度下解决问题。
这是一道非常经典、也非常值得反复理解的贪心题。 因为它会让你真正体会到:
-
有时候不需要枚举所有路径
-
真正关键的是抓住问题中的核心状态
-
贪心并不是“随便选”,而是维护最重要的全局信息
把这道题真正吃透之后,再去看:
-
跳跃游戏 II
-
加油站
-
分发饼干
-
区间覆盖类题目
你会更容易理解“贪心到底在贪什么”。










