【LeetCode Hot100 刷题日记 (79/100)】45. 跳跃游戏 II —— 贪心算法、数组、动态规划思想(非DP解法)🧠

1 阅读6分钟

📌 题目链接: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)

关键点在于:不是每一步都跳最远,而是每一步选择“未来能跳得最远”的位置——这就是贪心的核心思想。


🧠 核心算法及代码讲解

本题有两种主流解法:

  1. 反向贪心(从终点往起点找) :时间复杂度 O(n²),不推荐用于大规模数据。
  2. 正向贪心(一次遍历,维护当前跳跃能覆盖的最远边界)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 时错误地多加一次跳跃。


🧩 解题思路(分步拆解)

  1. 初始化

    • maxPos = 0:初始最远只能到 0(起点)。
    • end = 0:第一跳的边界就是起点。
    • step = 0:尚未跳跃。
  2. 遍历数组(除最后一个元素)

    • 对每个位置 i,尝试更新 maxPos = max(maxPos, i + nums[i])

    • 如果 i 到达了当前跳跃的边界 end,说明必须跳了

      • end 更新为 maxPos(即下一跳能覆盖的最远范围)。
      • step++
  3. 返回 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

🎤 面试延伸问题(提前准备!)

  1. 如果数组中可能无法到达终点,如何修改算法?
    → 在循环中若 maxPos < i,说明卡住了,返回 -1。
  2. 能否输出具体的跳跃路径?
    → 需要记录每次跳跃的落点,可用额外数组或回溯,但会增加空间复杂度。
  3. 和 BFS 有什么关系?
    → 本题本质是求最短路径(最少跳数),BFS 也能解,但时间 O(n²),空间 O(n)。贪心是 BFS 的优化特例(因为边权为1且结构特殊)。
  4. 为什么贪心在这里成立?
    → 因为“能跳得更远的位置永远不会比跳得近的位置差”——具有贪心选择性质最优子结构

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!