跳跃游戏 II 图解题解
这道题到底在问什么
- 输入
- nums = [2,3,1,1,4]
- 输出
- 2 (0→1→4:先跳到下标1,再一步跳到下标4)
最优解:为什么这么做
一句话答案:LeetCode 45 跳跃游戏 II:求从下标 0 到末尾的最少跳数,贪心按每跳能摸到的最远边界分层、走到本层尽头就跳一次,每跳都伸到最远换来最少层数,时间 O(n)、空间 O(1)。
从下标 0 跳到末尾,最少跳几次
nums[i] 是站在下标 i 时最多能往右跳的步数——写着 3,就是往右 1 步到 3 步随你挑。从下标 0 出发,题目保证一定跳得到最后一格,问最少跳几次。题面例子 nums=[2,3,1,1,4],答案是 2:先落到下标 1,再一步跳到下标 4。
逐格记最少跳数,为什么会到 O(n²)
按定义硬记的话,给每格标一个『到这里最少跳几次』:下标 0 是 0,之后每到一格,回头看前面所有能一步跳到它的格子,挑里面最小的跳数加一。麻烦在每标一格都要往回扫一遍前面的格子,n 格摞起来是 n×n 级比较,数据一大就掉进 O(n²),操作量随规模翻平方地涨。
不盯具体落点,只盯这一跳能摸到多远
换成贪心来跳,每一步只做当下最划算的选择、不回头。这里最划算的不是『跳到哪个具体格子』,而是让下一跳的落脚范围尽量往右够。道理在于:当前这一跳能落脚的格子里,不管你停在哪一格,决定后面还要跳几次的,只是它们当中谁能摸到最远的下标。既然落哪都不影响,就把这一跳折成一个数——它能延伸到的最远边界。
于是分层来看:第一跳从下标 0 覆盖的一段是第一层,这层里所有格子能摸到的最远处,就是第二跳的右边界、第二层的尽头。走到一层的尽头,就必须再跳一次进下一层。每层都把边界伸到最远,层数压到最少,跳数就最少。
end、farthest、jumps 各盯一件事
落到代码就三个变量:end 是当前这一跳的右边界,farthest 是边扫边探到的、下一跳能达到的最远处,jumps 是已跳次数。从左往右扫,每到一格 i 就用 i+nums[i] 更新 farthest、对整段区间取最大——区间里靠后的某格可能摸得更远。一旦 i 撞上 end,说明这一跳的落脚区间扫完了,jumps 加一,并把 end 推到 farthest 进入下一层。
[2,3,1,1,4] 上手扫一遍,2 跳怎么来的
三个变量都从 0 起。先看下标 0:farthest 取 max(0,0+2)=2;它正好等于 end=0,跳一次让 jumps=1,end 推到 2。到下标 1:farthest 取 max(2,1+3)=4,可还没到 end=2,只更新不跳。到下标 2:farthest 取 max(4,2+1)=4,这次撞上 end=2,再跳一次 jumps=2,end 推到 4。到下标 3:farthest 取 max(4,3+1)=4,没到 end=4,不跳。下标 4 是终点不进循环,扫到下标 3 就收手,返回 jumps=2。
循环边界和落点,一写偏就得多跳
循环若写到最后一格才停,站上终点还会再判一次 i==end、白多跳一回,写成扫到倒数第二格为止才对。撞到 end 时别急着拿 nums[i] 当落点往前挪,贪心要的是整段区间摸到的最远边界 farthest,具体停哪格不影响后面的跳数。farthest 也不能只看当前这一格的 i+nums[i],得对整段区间一路取最大,区间里更靠后的某格常常伸得更远,漏了 max 就会少算一跳。这套扫法把数组从左到右过一遍、每格做常数次比较,时间 O(n);只用三个变量、不开数组,空间 O(1)。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3记住三个变量:end = 当前这一跳的右边界,farthest = 当前区间里能摸到的最远处,jumps = 已跳次数。下面每一帧都在套这条规则:扫到 end 就「被迫」跳一次。
- 4一开始 end 指向下标 0(用 r 标),farthest 也是 0,还没跳。注意 farthest 不是「下一步跳到哪」,而是「在当前能落脚的范围内,往前最远能摸到哪个下标」。
- 5处理下标 0:它写着 2,所以从这里最远能摸到下标 0+2=2。把它和之前记的 farthest=0 比一比,取大的 → farthest=2。绿色高亮的就是当前这一跳能摸到的最远位置。
- 6关键时刻:下标 0正好踩在当前这一跳的右边界 end=0 上。说明「当前这一跳能落脚的区间已经扫完」,再往前走就超出这一跳了——所以必须跳一次(jumps=1),并把下一跳的右边界推到刚才记下的 farthest=2。看 r 指针往右跳了一格。
- 7处理下标 1:它写着 3,所以从这里最远能摸到下标 1+3=4。把它和之前记的 farthest=2 比一比,取大的 → farthest=4。绿色高亮的就是当前这一跳能摸到的最远位置。
- 8下标 1还没到当前跳的右边界 end=2(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 4。跳数保持 1 不变,继续往右。
- 9处理下标 2:它写着 1,所以从这里最远能摸到下标 2+1=3。把它和之前记的 farthest=4 比一比,取大的 → farthest=4。绿色高亮的就是当前这一跳能摸到的最远位置。
- 10关键时刻:下标 2正好踩在当前这一跳的右边界 end=2 上。说明「当前这一跳能落脚的区间已经扫完」,再往前走就超出这一跳了——所以必须跳一次(jumps=2),并把下一跳的右边界推到刚才记下的 farthest=4。看 r 指针往右跳了一格。
- 11处理下标 3:它写着 1,所以从这里最远能摸到下标 3+1=4。把它和之前记的 farthest=4 比一比,取大的 → farthest=4。绿色高亮的就是当前这一跳能摸到的最远位置。
- 12下标 3还没到当前跳的右边界 end=4(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 4。跳数保持 2 不变,继续往右。
- 13处理下标 4:它写着 4,所以从这里最远能摸到下标 4+4=8。把它和之前记的 farthest=4 比一比,取大的 → farthest=8。绿色高亮的就是当前这一跳能摸到的最远位置。
- 14关键时刻:下标 4正好踩在当前这一跳的右边界 end=4 上。说明「当前这一跳能落脚的区间已经扫完」,再往前走就超出这一跳了——所以必须跳一次(jumps=3),并把下一跳的右边界推到刚才记下的 farthest=8。看 r 指针往右跳了一格。
- 15处理下标 5:它写着 2,所以从这里最远能摸到下标 5+2=7。把它和之前记的 farthest=8 比一比,取大的 → farthest=8。绿色高亮的就是当前这一跳能摸到的最远位置。
- 16下标 5还没到当前跳的右边界 end=8(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 8。跳数保持 3 不变,继续往右。
- 17处理下标 6:它写着 1,所以从这里最远能摸到下标 6+1=7。把它和之前记的 farthest=8 比一比,取大的 → farthest=8。绿色高亮的就是当前这一跳能摸到的最远位置。
- 18下标 6还没到当前跳的右边界 end=8(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 8。跳数保持 3 不变,继续往右。
- 19处理下标 7:它写着 3,所以从这里最远能摸到下标 7+3=10。把它和之前记的 farthest=8 比一比,取大的 → farthest=10。绿色高亮的就是当前这一跳能摸到的最远位置。
- 20下标 7还没到当前跳的右边界 end=8(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 10。跳数保持 3 不变,继续往右。
- 21处理下标 8:它写着 1,所以从这里最远能摸到下标 8+1=9。把它和之前记的 farthest=10 比一比,取大的 → farthest=10。绿色高亮的就是当前这一跳能摸到的最远位置。
- 22关键时刻:下标 8正好踩在当前这一跳的右边界 end=8 上。说明「当前这一跳能落脚的区间已经扫完」,再往前走就超出这一跳了——所以必须跳一次(jumps=4),并把下一跳的右边界推到刚才记下的 farthest=10。看 r 指针往右跳了一格。
- 23处理下标 9:它写着 1,所以从这里最远能摸到下标 9+1=10。把它和之前记的 farthest=10 比一比,取大的 → farthest=10。绿色高亮的就是当前这一跳能摸到的最远位置。
- 24下标 9还没到当前跳的右边界 end=10(i<end),所以这一格不触发跳跃——它只是帮我们「探路」,把 farthest 顺手更新到 10。跳数保持 4 不变,继续往右。
- 25扫描结束,落到终点下标 10。整条路上一共跳了 4 次。注意我们从不在循环里真的「移动落点」,而是用 end/farthest 算清「每一跳的覆盖区间」,扫到边界就跳——这正是贪心给出最少跳数的原因。
⚠️ 容易写错的地方
✗ 错:循环写到 i == n-1
✓ 对:循环到 i < n-1 即可
站上最后一格就到终点了,若在最后一格还判 i==end 会多跳一次
✗ 错:一到 end 就立刻用 nums[i] 当落点
✓ 对:跳的目标是 farthest 而非某个具体格子
贪心要的是「区间内能摸到的最远」,具体落在哪格不重要
✗ 错:farthest 只看当前格 i+nums[i]
✓ 对:farthest 要对整段区间取最大
当前跳区间里某个更靠后的格子可能摸得更远,必须 max 累计
完整代码(Python / C++ / Java)
Python
def jump(nums):
jumps = end = farthest = 0
for i in range(len(nums) - 1): # 最后一格不用再起跳
farthest = max(farthest, i + nums[i]) # 当前区间能摸到的最远
if i == end: # 扫到当前跳的右边界
jumps += 1 # 必须跳一次
end = farthest # 下一跳右边界 = farthest
return jumpsC++
int jump(vector<int>& nums) {
int jumps = 0, end = 0, farthest = 0;
for (int i = 0; i + 1 < (int)nums.size(); ++i) {
farthest = max(farthest, i + nums[i]); // 区间内最远
if (i == end) { // 到当前跳右边界
++jumps; // 跳一次
end = farthest; // 更新下一跳右边界
}
}
return jumps;
}Java
public int jump(int[] nums) {
int jumps = 0, end = 0, farthest = 0;
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]); // 区间内最远
if (i == end) { // 到当前跳右边界
jumps++; // 跳一次
end = farthest; // 更新下一跳右边界
}
}
return jumps;
}复杂度
时间
O(n)
只把数组从左到右扫一遍,每格做常数次比较
空间
O(1)
只用 jumps/end/farthest 三个变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 跳跃游戏 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么贪心一定给的是最少跳数,不会有更省的跳法?+
把每一跳能新覆盖到的下标看成一层:第 0 层只有起点,第 1 层是从起点一步能到的所有格子,第 2 层是从第 1 层再跳一步能到的……找最少跳数就是看终点落在第几层。end 记当前层的右边界、farthest 记下一层的右边界,扫到 end 就进下一层跳一次,本质是把 BFS 一层层扩展压成了一次从左到右的扫描。BFS 找最短路天然给最少层数,层数最少即跳数最少。
end 和 farthest 到底差在哪,为什么要两个?+
end 是『当前这一跳』已经定死的右边界,一轮扫描里不动;farthest 是在当前区间里边走边探到的、下一跳能达到的最远处,每格都可能刷新它。只有扫到 i==end 时,才把 end 一次性推到 farthest——相当于当前层走到头、跳进下一层。少了 farthest 就不知道下一跳能伸多远,少了 end 就不知道什么时候该跳,两个各管一头。
撞到 end 时,为什么跳的目标是 farthest 而不是当前格能到的某个具体位置?+
因为具体落在哪一格根本不影响后面还要跳几次,唯一起作用的是下一跳能覆盖到的最远边界。当前这一跳的区间里,可能是靠后的某格才摸得最远,farthest 已经把整段区间里最远的那个探好了。真去指定一个落点,反而可能挑中一个伸不远的格子,白白浪费一跳。所以只认 farthest,不认具体落点。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 跳跃游戏 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。