一、问题定义与核心挑战
1.1 问题描述
给定一个非负整数数组 nums
,你最初位于数组的第一个下标。数组中的每个元素 nums[i]
表示你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个下标。假设你总能到达数组的最后一个下标。
1.2 核心特征与难点
- 最优性要求:不仅要判断可达性,还要找到最少跳跃次数
- 跳跃规则 :从位置
i
可跳到i+1
至i+nums[i]
之间的任意位置 - 贪心选择:如何在每一步选择最优的跳跃点以最小化总次数
示例:
- 输入:
nums = [2,3,1,1,4]
- 输出:
2
(最优路径:0→1→4
,两次跳跃)
二、解题思路:基于覆盖范围的贪心策略
2.1 核心思想
通过跟踪当前覆盖范围 和下一步最大覆盖范围,在每次到达当前覆盖范围的边界时进行跳跃,从而保证每次跳跃都是最优的:
- 当前覆盖范围(cover):当前跳跃所能到达的最远位置
- 下一步最大覆盖范围(maxCover):在当前覆盖范围内,所有位置能到达的最远位置
- 跳跃时机:当遍历到当前覆盖范围的边界时,必须进行一次跳跃,此时将当前覆盖范围更新为下一步最大覆盖范围
2.2 直观理解
把跳跃过程想象成多阶段冲刺:
- 第一阶段:从起点出发,能到达的范围是
[0, cover]
,在这个范围内寻找能跳得最远的点,确定下一阶段的最大范围maxCover
- 当到达第一阶段的边界(
i = cover
)时,必须跳一次,进入第二阶段,此时cover
更新为maxCover
- 重复上述过程,直到覆盖范围包含终点
三、代码逐行解析
3.1 边界条件处理
java
if (nums.length == 1) {
return 0; // 只有一个元素时,无需跳跃
}
3.2 核心变量定义
java
int cover = 0; // 当前覆盖范围的边界
int maxCover = 0; // 下一步能到达的最大覆盖范围
int cnt = 0; // 跳跃次数
3.3 遍历与跳跃逻辑
java
for (int i = 0; i < nums.length; i++) {
// 持续更新下一步能到达的最大覆盖范围
maxCover = Math.max(i + nums[i], maxCover);
// 当到达当前覆盖范围的边界,且能跳得更远时
if (i == cover && maxCover > cover) {
cover = maxCover; // 更新覆盖范围
cnt++; // 跳跃次数加1
// 若已覆盖终点,提前返回
if (cover >= nums.length - 1) {
return cnt;
}
}
}
3.4 返回结果
java
return cnt;
四、算法执行过程演示
以 nums = [2,3,1,1,4]
为例:
索引 i |
当前元素 nums[i] |
maxCover 计算 |
关键操作 | cover |
cnt |
---|---|---|---|---|---|
0 | 2 | max(0+2=2, 0) = 2 | - | 0 | 0 |
1 | 3 | max(1+3=4, 2) = 4 | - | 0 | 0 |
2 | 1 | max(2+1=3, 4) = 4 | i=2 == cover=0?不 | 0 | 0 |
3 | 1 | max(3+1=4, 4) = 4 | i=3 == cover=0?不 | 0 | 0 |
4 | 4 | max(4+4=8, 4) = 8 | i=4 == cover=0?是 maxCover>cover → 更新 cover=4,cnt=1 cover≥4(终点)→ 返回 1? | 4 | 1 |
注意:实际执行中,当 i=2 时已经满足 i==cover(0),此时会触发跳跃:
- 在 i=2 时,i==cover(0)且 maxCover=4>0 → cover=4,cnt=1
- 此时 cover=4 已包含终点(4),直接返回 cnt=1?
修正演示:
- i=0 时,maxCover=2
- i=1 时,maxCover=4
- i=2 时,i==cover(0)→ 触发跳跃,cover=4,cnt=1,此时 cover≥4 → 返回 1
最终结果为 2
?不,正确结果应为 2,这里需要更精确的步骤分析:
正确步骤:
- 初始状态:cover=0,maxCover=0,cnt=0
- i=0:
- maxCover = max(0+2, 0) = 2
- i != cover(0==0),但需判断是否更新
- 此时 i==cover 且 maxCover>cover → cover=2,cnt=1
- i=1:
- maxCover = max(1+3=4, 2) =4
- i=2:
- icover(22)
- maxCover=4>2 → cover=4,cnt=2
- 此时 cover≥4(终点)→ 返回 2
五、核心逻辑深度解析
5.1 为什么在 i == cover
时跳跃?
i == cover
表示已到达当前跳跃能覆盖的最远距离,此时必须进行下一次跳跃才能继续前进。选择此时跳跃能保证每次跳跃都是必要的,且跳跃范围是当前能达到的最大可能,从而最小化跳跃次数。
5.2 为什么 maxCover
能保证最优性?
maxCover
记录了当前覆盖范围内所有位置能到达的最远距离,选择这个范围作为下一次跳跃的覆盖范围,确保了每次跳跃都能到达最远的位置,从而减少总跳跃次数。
5.3 为什么无需考虑具体跳跃到哪个位置?
贪心算法的核心是"只关注范围不关注具体点"。只要知道下一次能覆盖的最大范围,就能确定最少跳跃次数,无需记录具体跳到哪个位置。
六、算法复杂度分析
- 时间复杂度:O(n),只需遍历数组一次
- 空间复杂度:O(1),仅使用常数个变量
七、常见误区与优化说明
7.1 误区1:在每次循环中都尝试跳跃
错误地在每个位置都进行跳跃判断,会导致跳跃次数过多。正确做法是只在到达当前覆盖范围边界时才跳跃。
7.2 误区2:忽略 maxCover > cover
的判断
如果没有这个判断,当无法跳得更远时(如 nums = [0,0,0]
),会错误地增加跳跃次数。
7.3 优化点:提前终止循环
当 cover
已覆盖终点时,可立即返回结果,无需遍历剩余元素。
八、总结:贪心策略的跳跃优化
本题的贪心解法通过跟踪覆盖范围实现了最少跳跃次数的求解,核心在于:
- 区分当前覆盖范围和下一步最大覆盖范围
- 在适当的时机(到达当前范围边界)进行跳跃
- 每次跳跃都选择能到达的最远范围
这种策略确保了每一步都是最优的,从而在整体上得到最少的跳跃次数。掌握这种基于范围的贪心思想,可有效解决类似的优化路径问题。
代码的关键在于通过两个变量 cover
和 maxCover
分别跟踪当前覆盖范围和下一步能到达的最大范围,在到达当前范围边界时进行跳跃并更新范围,从而保证每次跳跃都是最优的,最终得到最少的跳跃次数。