📌 题目链接:45. 跳跃游戏 II - 力扣(LeetCode)
🔍 难度:中等 | 🏷️ 标签:贪心算法、数组、动态规划思想(非DP解法)
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
在 LeetCode 的经典贪心题型中,「跳跃游戏 II」 是一道极具代表性的题目。它不仅考察你对“局部最优推导全局最优”的理解,还要求你能精准控制边界、避免冗余操作。本题是 Hot100 中贪心策略的标杆题,也是高频面试题(尤其在字节、腾讯、阿里等大厂的算法轮中频繁出现)。
今天我们就来彻底吃透这道题,从问题本质到代码细节,再到面试可能被追问的变种与陷阱,一网打尽!
🔍 题目分析
给定一个非负整数数组 nums,你从索引 0 出发。
nums[i] 表示你在位置 i 最多可以向右跳 nums[i] 步(即能到达 [i+1, i+nums[i]] 范围内的任意位置)。
目标:求出到达最后一个位置(n-1)所需的最少跳跃次数。
题目保证一定可以到达终点,所以无需处理无解情况。
✅ 示例回顾:
nums = [2,3,1,1,4]→ 最少跳 2 次(0→1→4)nums = [2,3,0,1,4]→ 最少跳 2 次(0→1→4)
关键点在于:不是每一步都跳最远,而是每一步选择“未来能跳得最远”的位置——这就是贪心的核心思想。
🧠 核心算法及代码讲解
本题有两种主流解法:
- 反向贪心(从终点往起点找) :时间复杂度 O(n²),不推荐用于大规模数据。
- 正向贪心(一次遍历,维护当前跳跃能覆盖的最远边界) :O(n) 时间 + O(1) 空间,最优解!
我们重点讲解 方法二:正向贪心,这也是面试官期望听到的解法。
✨ 核心思想
-
我们不需要知道具体每一步跳到哪个下标,只需要知道在当前这一跳的范围内,下一步能跳到的最远位置。
-
维护两个关键变量:
maxPos:当前所有可选位置中,能跳到的最远下标。end:当前这一跳的最远边界。一旦遍历到end,说明必须进行下一次跳跃,并更新end = maxPos。
💡 类比理解:
就像你在玩“跳格子”游戏,每一跳你站在一个平台,这个平台能让你看到前方一片区域。你不需要立刻决定落点,但你要记住在这片区域内,哪个新平台能让你看得更远。当你走到当前平台尽头时,就必须跳到那个“看得最远”的新平台——这就是一次跳跃。
📜 C++ 代码(带逐行注释)
class Solution {
public:
int jump(vector<int>& nums) {
int maxPos = 0; // 当前所有可到达位置中,能跳到的最远下标
int end = 0; // 当前这一跳的最远边界(即本次跳跃能覆盖的右端点)
int step = 0; // 跳跃次数
int n = nums.size();
// 注意:只遍历到 n-2(即倒数第二个元素)
// 因为当 i == n-1 时已经到达终点,无需再跳
for (int i = 0; i < n - 1; ++i) {
// 更新当前能到达的最远位置
maxPos = max(maxPos, i + nums[i]);
// 如果到达了当前跳跃的边界,必须进行下一次跳跃
if (i == end) {
end = maxPos; // 更新边界为当前能跳到的最远位置
++step; // 跳跃次数 +1
}
}
return step;
}
};
✅ 为什么
i < n - 1?
因为当i == n-1时,我们已经站在终点,不需要再跳。如果包含i = n-1,可能会在end == n-1时错误地多加一次跳跃。
🧩 解题思路(分步拆解)
-
初始化:
maxPos = 0:初始最远只能到 0(起点)。end = 0:第一跳的边界就是起点。step = 0:尚未跳跃。
-
遍历数组(除最后一个元素) :
-
对每个位置
i,尝试更新maxPos = max(maxPos, i + nums[i])。 -
如果
i到达了当前跳跃的边界end,说明必须跳了:- 把
end更新为maxPos(即下一跳能覆盖的最远范围)。 step++。
- 把
-
-
返回
step:即最少跳跃次数。
🎯 关键洞察:
“跳的次数”由“边界更新次数”决定,而不是由“走了多少步”决定。
这正是贪心策略的精髓:延迟决策——不到边界不跳,跳就跳到最优位置。
📊 算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n) :仅一次遍历,每个元素访问一次 |
| 空间复杂度 | O(1) :仅使用常数个额外变量 |
| 是否稳定 | 是,确定性算法 |
| 适用场景 | 所有“最少步数到达终点”类问题(如 BFS 可解但数据量大时) |
| 面试加分点 | 能解释为何不遍历最后一个元素;能对比反向贪心的劣势 |
⚠️ 常见错误:
- 在循环中写
i <= n-1→ 多跳一次。- 初始化
step = 1→ 忽略了起点即终点的情况(但本题 n≥1 且保证可达,n=1 时应返回 0)。
💻 完整代码
✅ C++ 版本
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
int jump(vector<int>& nums) {
int maxPos = 0, n = nums.size(), end = 0, step = 0;
for (int i = 0; i < n - 1; ++i) {
if (maxPos >= i) {
maxPos = max(maxPos, i + nums[i]);
if (i == end) {
end = maxPos;
++step;
}
}
}
return step;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
vector<int> nums1 = {2,3,1,1,4};
cout << sol.jump(nums1) << "\n"; // 输出: 2
vector<int> nums2 = {2,3,0,1,4};
cout << sol.jump(nums2) << "\n"; // 输出: 2
vector<int> nums3 = {1};
cout << sol.jump(nums3) << "\n"; // 输出: 0
return 0;
}
✅ JavaScript 版本
var jump = function(nums) {
let maxPos = 0, end = 0, steps = 0;
for (let i = 0; i < nums.length - 1; ++i) {
if (maxPos >= i) {
maxPos = Math.max(maxPos, i + nums[i]);
if (i === end) {
end = maxPos;
++steps;
}
}
}
return steps;
};
// 测试
console.log(jump([2,3,1,1,4])); // 2
console.log(jump([2,3,0,1,4])); // 2
console.log(jump([1])); // 0
🎤 面试延伸问题(提前准备!)
- 如果数组中可能无法到达终点,如何修改算法?
→ 在循环中若maxPos < i,说明卡住了,返回 -1。 - 能否输出具体的跳跃路径?
→ 需要记录每次跳跃的落点,可用额外数组或回溯,但会增加空间复杂度。 - 和 BFS 有什么关系?
→ 本题本质是求最短路径(最少跳数),BFS 也能解,但时间 O(n²),空间 O(n)。贪心是 BFS 的优化特例(因为边权为1且结构特殊)。 - 为什么贪心在这里成立?
→ 因为“能跳得更远的位置永远不会比跳得近的位置差”——具有贪心选择性质和最优子结构。
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!