题目描述
思路解析
一句话答案: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)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
为什么只盯一个 maxReach 就够?因为「能到的格子」一定是从 0 开始一段连续的前缀——只要 i 没越过 maxReach,i 就可达,它就能把边界往前推到 i+nums[i]。不用关心具体怎么跳,只看边界有没有断。
例 1(可达) 开始。蓝色光标 cur 停在出发点下标 0,maxReach 先记成 0——还没跳,能到的最远就是脚下。下面一格一格往右扫,看 maxReach 能不能被推到 9。
光标到下标 0。先检查:0 没有超过 maxReach=0(黄色标的就是当前最远),所以 0 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
把候选 0+2=2 和旧 maxReach=0 取较大,maxReach 推到了 2(绿色那格)。可达边界又往前挪了一截,接着看下一格。
光标到下标 1。先检查:1 没有超过 maxReach=2(黄色标的就是当前最远),所以 1 是能踩到的。它写着 3,意味着从这里最多能再往前跳 3 步。
把候选 1+3=4 和旧 maxReach=2 取较大,maxReach 推到了 4(绿色那格)。可达边界又往前挪了一截,接着看下一格。
光标到下标 2。先检查:2 没有超过 maxReach=4(黄色标的就是当前最远),所以 2 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
候选 2+1=3 没超过旧 maxReach=4,所以最远边界不变,还是 4(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 3。先检查:3 没有超过 maxReach=4(黄色标的就是当前最远),所以 3 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
候选 3+1=4 没超过旧 maxReach=4,所以最远边界不变,还是 4(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 4。先检查:4 没有超过 maxReach=4(黄色标的就是当前最远),所以 4 是能踩到的。它写着 4,意味着从这里最多能再往前跳 4 步。
把候选 4+4=8 和旧 maxReach=4 取较大,maxReach 推到了 8(绿色那格)。可达边界又往前挪了一截,接着看下一格。
光标到下标 5。先检查:5 没有超过 maxReach=8(黄色标的就是当前最远),所以 5 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
候选 5+2=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 6。先检查:6 没有超过 maxReach=8(黄色标的就是当前最远),所以 6 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
候选 6+1=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 7。先检查:7 没有超过 maxReach=8(黄色标的就是当前最远),所以 7 是能踩到的。它写着 0,意味着从这里最多能再往前跳 0 步。
候选 7+0=7 没超过旧 maxReach=8,所以最远边界不变,还是 8(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 8。先检查:8 没有超过 maxReach=8(黄色标的就是当前最远),所以 8 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
用下标 8 刷新:maxReach 变成 10,已经够到(甚至越过)末尾下标 9。终点进了可达范围——不用再扫了,答案 true。绿色高亮的就是被够到的终点边界。
光标到下标 9。先检查:9 没有超过 maxReach=10(黄色标的就是当前最远),所以 9 是能踩到的。它写着 5,意味着从这里最多能再往前跳 5 步。
用下标 9 刷新:maxReach 变成 14,已经够到(甚至越过)末尾下标 9。终点进了可达范围——不用再扫了,答案 true。绿色高亮的就是被够到的终点边界。
例 1(可达) 扫完:可达边界一路被推过了末尾,绿色高亮的最后一格在可达范围内,答案是 true。
例 2(不可达) 开始。蓝色光标 cur 停在出发点下标 0,maxReach 先记成 0——还没跳,能到的最远就是脚下。下面一格一格往右扫,看 maxReach 能不能被推到 4。
光标到下标 0。先检查:0 没有超过 maxReach=0(黄色标的就是当前最远),所以 0 是能踩到的。它写着 3,意味着从这里最多能再往前跳 3 步。
把候选 0+3=3 和旧 maxReach=0 取较大,maxReach 推到了 3(绿色那格)。可达边界又往前挪了一截,接着看下一格。
光标到下标 1。先检查:1 没有超过 maxReach=3(黄色标的就是当前最远),所以 1 是能踩到的。它写着 2,意味着从这里最多能再往前跳 2 步。
候选 1+2=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 2。先检查:2 没有超过 maxReach=3(黄色标的就是当前最远),所以 2 是能踩到的。它写着 1,意味着从这里最多能再往前跳 1 步。
候选 2+1=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
光标到下标 3。先检查:3 没有超过 maxReach=3(黄色标的就是当前最远),所以 3 是能踩到的。它写着 0,意味着从这里最多能再往前跳 0 步。
候选 3+0=3 没超过旧 maxReach=3,所以最远边界不变,还是 3(绿色那格)。这一格跳得近,但只要没越界就不影响结论,继续扫。
走到下标 4,可此刻 maxReach 才 3——4 已经超出了能到达的最远边界。意味着从 0 出发无论怎么跳都落不到这一格,路被堵死,直接 false。红色就是卡死的位置。
例 2(不可达) 卡死在下标 4(红色):它超出了能到达的最远边界,前面那个 0 让所有路线都停在了它前面,到不了终点,答案 false。
边界先想清:长度为 1 时直接 true(人已在终点);其余看 maxReach 这条边界能不能一路推到末尾。
两个高频追问:能不能到看「连续前缀边界是否断」;要最少步数则升级为 Jump Game II 的分层贪心。
参考代码
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 True复杂度
- 时间:O(n),从左到右扫一遍数组,每格只看一次
- 空间:O(1),只用一个 maxReach 变量,不开额外数组
易错点
面试追问把动画讲成自己的话
追问为什么只维护一个 maxReach 就够,不用记具体怎么跳?
追问如果还要求最少跳几次到末尾呢?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
跳跃游戏 II
LeetCode 45 · 中等 · 沿着 贪心 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题