跳跃游戏 图解题解
这道题到底在问什么
- 输入
- nums=[2,3,1,1,4]
- 输出
- true (0→1→4,或 0→2→…,能到末尾)
- 输入
- nums=[3,2,1,0,4]
- 输出
- false (到下标 3 是个 0,跳不出去)
最优解:为什么这么做
一句话答案:LeetCode 55 跳跃游戏的最优解是贪心:从左到右扫数组,维护一个 maxReach 表示当前能到达的最远下标。扫到 i 时若 i 已超过 maxReach,说明可达边界断了,直接返回 false;否则用 i + nums[i] 刷新边界,边界一旦够到最后一个下标就返回 true。一遍扫描,时间 O(n)、空间 O(1)。
跳跃游戏这道题在问什么
从下标 0 出发,nums[i] 表示站在 i 处最多能往前跳的步数——注意是「最多」,跳 1 步到 nums[i] 步之间随便选。问能否到达最后一个下标,答案只要 true 或 false。这是个存在性问题:不问最少跳几次,也不用给出跳法,只判定「有没有一条路」。抓住这一点,就不必执着于还原具体路线。
为什么不能枚举每一种跳法
直觉做法是搜索:站在每个位置,把 1 到 nums[i] 步的每种跳法都递归试一遍。可行是可行,但路线数量随数组长度指数增长,必然超时;即便加记忆化做成 DP,也要 O(n²) 级别的转移。反过来想,题目只要一个布尔答案,为每条具体路线付出代价本身就是浪费——存在性问题往往有比「逐一枚举方案」粗得多的判据,这道题的判据就藏在可达位置的形状里。
为什么一个 maxReach 变量就够了
关键观察:能到达的下标集合,一定是从 0 开始的一段连续前缀,中间不会有洞。理由很简单——nums[i] 是「最多」跳的步数,能从某个可达位置跳到 j,就能从同一个位置少跳几步落在 j 之前的任何格子上,所以「能到 j」蕴含「j 左边全都能到」。
既然可达集是一段连续区间 [0, maxReach],描述它只需要右端点这一个数。整道题于是被压缩成:从左到右扫,看这条边界能被推到多远、中途有没有断——这就是贪心算法只维护一个 maxReach 的底气,不用记任何具体跳法。
扫描中每一步的判定为什么成立
扫到下标 i 时先检查:若 i <= maxReach,说明 i 落在可达前缀里,i 本身可达,那么从 i 起跳能覆盖到 i + nums[i],用它刷新 maxReach,即 maxReach = max(maxReach, i + nums[i]);一旦 maxReach >= n - 1,终点已进入可达范围,提前返回 true。若 i > maxReach,说明前面所有位置合力也够不到 i,而 i 右边的格子只会更远,边界永远断在这里,返回 false。
示例 [3,2,1,0,4] 正是断链的典型:前四个位置把边界最多推到 3,下标 4 够不着,false。它同时说明「遇到 0 就完蛋」是误判——0 本身不产生推进,但只要之前的边界已经越过它(比如 [2,3,1,1,4] 也含跳 1 步的格子照样能通),0 就只是路过的一格。真正的死因从来不是某个 0,而是边界恰好停在 0 上再也推不动。
复杂度多少,哪些细节容易写错
数组从左到右扫一遍,每个下标只做常数次比较和取 max,时间 O(n);除 maxReach 外不开任何额外结构,空间 O(1)。
最容易写错的是断链判定的边界:条件是 i > maxReach 才 false,写成 i >= maxReach 会把「恰好踩在边界上」的可达位置误判成够不着。另一个理解性的坑上一节已拆过:0 不必特判。延伸一问:如果要求最少跳几次到终点,那是跳跃游戏 II(LeetCode 45),贪心升级成分层推进边界,依然 O(n)。
▶ 动画逐步走查(共 34 步)——想跟着动画一帧帧对照就展开
- 3为什么只盯一个 maxReach 就够?因为「能到的格子」一定是从 0 开始一段连续的前缀——只要 i 没越过 maxReach,i 就可达,它就能把边界往前推到 i+nums[i]。不用关心具体怎么跳,只看边界有没有断。
- 4例 1(可达) 开始。蓝色光标 cur 停在出发点下标 0,maxReach 先记成 0——还没跳,能到的最远就是脚下。下面一格一格往右扫,看 maxReach 能不能被推到 9。
- 5光标到下标 0。先检查:0 没有超过 maxReach=0(黄色标的就是当前最远),所以 0 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
- 6把候选 0+2=2 和旧 maxReach=0 取较大,maxReach 推到了 2(绿色那格)。可达边界又往前挪了一截,接着看下一格。
- 7光标到下标 1。先检查:1 没有超过 maxReach=2(黄色标的就是当前最远),所以 1 是能踩到的。它写着 3,意味着从这里最多能再往前跳 3 步。
- 8把候选 1+3=4 和旧 maxReach=2 取较大,maxReach 推到了 4(绿色那格)。可达边界又往前挪了一截,接着看下一格。
- 9光标到下标 2。先检查:2 没有超过 maxReach=4(黄色标的就是当前最远),所以 2 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
- 10候选 2+1=3 没超过旧 maxReach=4,所以最远边界不变,还是 4(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 11光标到下标 3。先检查:3 没有超过 maxReach=4(黄色标的就是当前最远),所以 3 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
- 12候选 3+1=4 没超过旧 maxReach=4,所以最远边界不变,还是 4(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 13光标到下标 4。先检查:4 没有超过 maxReach=4(黄色标的就是当前最远),所以 4 是能踩到的。它写着 4,意味着从这里最多能再往前跳 4 步。
- 14把候选 4+4=8 和旧 maxReach=4 取较大,maxReach 推到了 8(绿色那格)。可达边界又往前挪了一截,接着看下一格。
- 15光标到下标 5。先检查:5 没有超过 maxReach=8(黄色标的就是当前最远),所以 5 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
- 16候选 5+2=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 17光标到下标 6。先检查:6 没有超过 maxReach=8(黄色标的就是当前最远),所以 6 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
- 18候选 6+1=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 19光标到下标 7。先检查:7 没有超过 maxReach=8(黄色标的就是当前最远),所以 7 是能踩到的。它写着 0,意味着从这里最多能再往前跳 0 步。
- 20候选 7+0=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 21光标到下标 8。先检查:8 没有超过 maxReach=8(黄色标的就是当前最远),所以 8 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
- 22用下标 8 刷新:maxReach 变成 10,已经够到(甚至越过)末尾下标 9。终点进了可达范围——不用再扫了,答案 true。绿色高亮的就是被够到的终点边界。
- 23光标到下标 9。先检查:9 没有超过 maxReach=10(黄色标的就是当前最远),所以 9 是能踩到的。它写着 5,意味着从这里最多能再往前跳 5 步。
- 24用下标 9 刷新:maxReach 变成 14,已经够到(甚至越过)末尾下标 9。终点进了可达范围——不用再扫了,答案 true。绿色高亮的就是被够到的终点边界。
- 25例 1(可达) 扫完:可达边界一路被推过了末尾,绿色高亮的最后一格在可达范围内,答案是 true。
- 26例 2(不可达) 开始。蓝色光标 cur 停在出发点下标 0,maxReach 先记成 0——还没跳,能到的最远就是脚下。下面一格一格往右扫,看 maxReach 能不能被推到 4。
- 27光标到下标 0。先检查:0 没有超过 maxReach=0(黄色标的就是当前最远),所以 0 是能踩到的。它写着 3,意味着从这里最多能再往前跳 3 步。
- 28把候选 0+3=3 和旧 maxReach=0 取较大,maxReach 推到了 3(绿色那格)。可达边界又往前挪了一截,接着看下一格。
- 29光标到下标 1。先检查:1 没有超过 maxReach=3(黄色标的就是当前最远),所以 1 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
- 30候选 1+2=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 31光标到下标 2。先检查:2 没有超过 maxReach=3(黄色标的就是当前最远),所以 2 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
- 32候选 2+1=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 33光标到下标 3。先检查:3 没有超过 maxReach=3(黄色标的就是当前最远),所以 3 是能踩到的。它写着 0,意味着从这里最多能再往前跳 0 步。
- 34候选 3+0=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
- 35走到下标 4,可此刻 maxReach 才 3——4 已经超出了能到达的最远边界。意味着从 0 出发无论怎么跳都落不到这一格,路被堵死,直接 false。红色就是卡死的位置。
- 36例 2(不可达) 卡死在下标 4(红色):它超出了能到达的最远边界,前面那个 0 让所有路线都停在了它前面,到不了终点,答案 false。
⚠️ 容易写错的地方
✗ 错:想枚举每一种跳法去试
✓ 对:只维护 maxReach 扫一遍
路线呈指数级,会超时;贪心只看「边界有没有断」就够了
✗ 错:判定写成 i >= maxReach 就 false
✓ 对:应是 i > maxReach 才 false
i == maxReach 时 i 这一格恰好可达,不能误判为到不了
✗ 错:遇到 0 就直接 false
✓ 对:0 不一定卡住
只要前面某格能「跨过」这个 0(maxReach 已越过它),就没事
完整代码(Python / C++ / Java)
Python
def canJump(nums):
max_reach = 0 # 目前能到达的最远下标
for i in range(len(nums)):
if i > max_reach: # 连 i 都到不了
return False # 边界断了,跳不过去
max_reach = max(max_reach, i + nums[i]) # 刷新最远
if max_reach >= len(nums) - 1:
return True # 已能够到末尾
return TrueC++
bool canJump(vector<int>& nums) {
int maxReach = 0; // 能到达的最远下标
int n = nums.size();
for (int i = 0; i < n; i++) {
if (i > maxReach) return false; // 到不了 i,断了
maxReach = max(maxReach, i + nums[i]); // 刷新最远
if (maxReach >= n - 1) return true; // 够到末尾
}
return true;
}Java
public boolean canJump(int[] nums) {
int maxReach = 0; // 能到达的最远下标
int n = nums.length;
for (int i = 0; i < n; i++) {
if (i > maxReach) return false; // 到不了 i,断了
maxReach = Math.max(maxReach, i + nums[i]); // 刷新最远
if (maxReach >= n - 1) return true; // 够到末尾
}
return true;
}复杂度
时间
O(n)
从左到右扫一遍数组,每格只看一次
空间
O(1)
只用一个 maxReach 变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 跳跃游戏 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么只维护一个 maxReach 就够,不用记具体怎么跳?+
因为能到达的下标一定是从 0 开始的一段连续前缀 [0..maxReach]。只要 i 落在这段里它就可达,就能把边界推到 i+nums[i]。我们只关心「边界有没有断」,与具体路线无关,所以一个 maxReach 足矣。
如果还要求最少跳几次到末尾呢?+
那是 Jump Game II,贪心升级:维护当前这一跳能覆盖的右边界 end 和这一步内能探到的最远 farthest,扫到 i==end 时跳数+1 并把 end 跳到 farthest,BFS 分层思想,仍是 O(n)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 跳跃游戏 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。