Hot 100 --- 跳跃游戏 II
本文概览:本文讲解跳跃游戏 II 的两种贪心写法——分层遍历(一跳一层,无需选落点)和落点选择(每次跳到能到最远的落点),本质都是"每跳取最远可达"
一、题目

二、题目分析
1. 题目要求
给定一个非负整数数组 nums,最初位于下标 0,每个元素代表在该位置能往前跳的最大步数。题目保证总是可以到达最后一个下标,求到达终点的最少跳跃次数。
例子:nums = [2, 3, 1, 1, 4]
1 | 0 1 2 3 4 ← 索引 |
最少 2 跳:从下标 0 跳 1 步到下标 1(值 3),再跳 3 步到终点下标 4。
和跳跃游戏 I 的区别:
- 跳跃游戏 I:判断能不能到终点
- 跳跃游戏 II:必然能到终点,求最少几跳
2. 核心工具还是"最远可达距离"
跳跃游戏 I 靠"最远可达距离 maxDistance"判断能不能到末尾。这题怎么利用它?
关键事实:既然必然能到终点,那么每一跳都跳到"当前能到的最远位置",整体就是最少跳数——每一跳都把进度推到最大,跳数自然最少。这是贪心思想。
基于这个事实,有两种编写视角,本质一样,写法不同:
- 方法一:分层遍历——把跳跃看成一层一层(隐式 BFS),一跳就是一层,走完一层跳数加一,无需选择落点
- 方法二:落点选择——真正模拟每一跳,站在落点上枚举候选,选"经过它能到最远"的那个落点跳过去
方法二直观好理解但代码边界多;方法一不需要选落点,顺序遍历完整走完就好,代码好写。下面两种都讲解。
三、思路概览
方法一:分层遍历(推荐,好写)
1 | public int jump(int[] nums) { |
思路简要说明:
- curEnd:当前这一跳能覆盖的右边界(当前层的范围终点)
- curFarthest:遍历当前层的过程中,不断刷新的"下一层右边界"
- i == curEnd 时:当前层走完了,必须再跳一次 → jumps++,进入下一层(curEnd = curFarthest)
- curEnd >= len - 1:下一层已覆盖终点,提前结束
- **时间复杂度 O(n)**:一遍遍历
方法二:落点选择(直观,边界多)
1 | public int jump(int[] nums) { |
思路简要说明:
- 站在落点 index:先看
index + nums[index]是否直接覆盖终点,是就收尾 - 枚举候选落点:
index+1到index+nums[index],计算每个候选 j 的"经过它最远到j + nums[j]" - 跳到最优落点:选能刷新 maxDistance 的那个,jump++
- 循环直到收尾:题目保证必然可达,最后一轮一定走 return
四、思路详解
方法一:分层遍历
第一步:把跳跃看成一层一层
从索引 0(值 2)出发,落点可以是索引 1 或索引 2——这些落点合在一起就是第 1 跳的可达范围,像一个"层":
1 | 0 1 2 3 4 ← 索引 |
- 第 1 跳的落点范围由
0 + nums[0] = 2决定,即 (0, 2] - 遍历索引 1、2 时,能到的最远被刷新为
1 + nums[1] = 4,这就是第 2 跳的右边界 - 第 2 跳的落点范围是 (2, 4],4 已覆盖终点
**每一层就是"固定跳数下的全部可达位置"**,这其实就是 BFS 的分层思想:第 k 层 = k 跳能到的所有位置。
第二步:层的右边界怎么转移?
用两个变量:
curEnd:当前层的右边界curFarthest:遍历当前层的过程中,不断被刷新的"最远可达",它就是下一层的右边界
1 | 遍历第1层(i 在 (0, 2] 内): |
每个位置 i 都贡献自己的 i + nums[i],取最大值——和跳跃游戏 I 的 maxDistance 一模一样,只是这里它是分层的:走完一层才结算一次。
第三步:什么时候 jumps++?
当 i == curEnd,说明当前层的范围已经走完,再往后走必然需要再跳一次:
1 | jumps++ ← 多跳一次 |
这就是"一次遍历、无需选择落点"的原因:我们从不关心具体落在哪,只要顺序扫过去,扫完一层自动结算一跳。每层的右边界取的都是该层所有位置贡献的最大值——每一跳都跳到最远,贪心就藏在层的定义里。
完整执行过程
以 nums = [2, 3, 1, 1, 4] 为例(len = 5,终点下标 4):
1 | 初始:jumps=0, curEnd=0, curFarthest=0 |
方法二:落点选择
第一步:每次跳跃怎么选落点?
站在落点 index,候选落点是 index+1 到 index+nums[index]。选哪个?
选"经过它能到最远"的那个。比如从索引 0 出发:
1 | 0 1 2 3 4 ← 索引 |
为什么这样对?因为落点选远的,下一跳的起点就更靠后、覆盖更远,总跳数只会更少不会更多——这就是"每一跳都取最远可达"的直接体现。
第二步:代码里的关键细节
收尾判断:每次落地先检查 index + distance >= len - 1,当前落点一步就能到终点,直接 return ++jump,不用再枚举。
候选计算:index + nums[index + i] + i 拆开看——
1 | index + i ← 候选落点的下标 |
maxDistance 跨轮次维护:它只增不减,记录的是"到目前为止见过的最远可达"。候选只有能刷新它才值得跳(否则有更远的走法覆盖了它)。
完整执行过程
以 nums = [2, 3, 1, 1, 4] 为例(len = 5):
1 | 初始:maxDistance=nums[0]=2, index=0, jump=0 |
两种思路的对比
| 维度 | 方法一:分层遍历 | 方法二:落点选择 |
|---|---|---|
| 视角 | 隐式 BFS,一跳一层 | 真实模拟每一跳 |
| 是否选落点 | 不选,顺序扫完一层自动结算 | 每跳枚举候选选最优 |
| 代码量 | 短,边界少 | 长,边界多(收尾、枚举、break) |
| 本质 | 每层右边界取层内贡献的最大值 | 每跳选能到最远的落点 |
| 复杂度 | O(n) / O(1) | O(n) / O(1)(每轮枚举的候选总数不超 n) |
本质完全一样:都是"每一跳取最远可达"。方法一把选择藏在层的结算里,方法二把选择明写在枚举里。理解了本质,推荐写方法一——不需要处理落点的边界,一遍遍历完整走完就好。
复杂度分析
**时间复杂度 O(n)**:两种方法都是每个索引常数次处理。
**空间复杂度 O(1)**:常数个变量。
五、总结
跳跃游戏 II 的核心思路:
- 题目前提:必然可达,求最少跳数
- 贪心策略:每一跳都取"能到的最远位置",跳数自然最少
- 方法一(分层遍历):curEnd 是当前层右边界,curFarthest 攒下一层右边界,
i == curEnd时 jumps++ 并换层,无需选落点 - 方法二(落点选择):每跳枚举候选落点,选"经过它两跳进度最大"的跳过去,当前落点能一步到终点就收尾
- 推荐方法一:边界少、好写,本质相同
和跳跃游戏 I 的关系:I 用 maxDistance 判断"能不能到",II 用同样的最远可达"数跳数"。I 是 II 的基础工具,II 是 I 的进阶应用——一个变量从"判可达"升级成了"分层计数"。
BFS 分层的思想在这里体现得很纯粹:**"最少跳数" = "最短路径",层就是步数**。遇到"求最少操作次数"类问题,想想能不能把状态按操作次数分层,一层一层往外扩。






