力扣解题-45. 跳跃游戏 II

4 阅读4分钟

力扣解题-45. 跳跃游戏 II

给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:

0 <= j <= nums[i] 且 i + j < n

返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1。

示例 1:

输入: nums = [2,3,1,1,4]

输出: 2

解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]

输出: 2

提示:

1 <= nums.length <= 104

0 <= nums[i] <= 1000

题目保证可以到达 n - 1


示例解答

解题思路

核心方法:贪心策略(边界优化版),通过维护“当前跳跃的最远边界”和“当前范围内下一跳的最远位置”,在遍历过程中精准控制跳跃时机,确保每一次跳跃都是“最优选择”,从而得到最小跳跃次数。时间复杂度O(n)、空间复杂度O(1),是本题的最优解法。

核心原理铺垫

跳跃游戏 II 的核心逻辑是:要得到最小跳跃次数,每一次跳跃都应选择能跳得最远的位置。通过两个关键变量实现这一策略:

  • currentEnd:当前这一跳能到达的最远边界,代表“必须触发下一次跳跃”的临界点;
  • farthest:在当前跳跃覆盖范围内,所有位置能跳到的最远位置,代表“下一跳的最优目标”;
  • jumps:记录跳跃次数,初始为0。
具体步骤
  1. 边界快速处理:若数组长度n <= 1,直接返回0(已在终点,无需跳跃);
  2. 初始化核心变量
    • jumps = 0:跳跃次数初始为0;
    • currentEnd = 0:初始跳跃边界为起点(下标0);
    • farthest = 0:初始下一跳最远位置为0;
  3. 遍历数组(关键优化:仅遍历到n-2)
    • 遍历下标i从0到n-2(无需遍历到最后一个下标,因为到达n-1即完成目标,无需再跳);
    • 更新farthestMath.max(farthest, i + nums[i])(记录当前范围内能跳到的最远位置);
    • i == currentEnd(到达当前跳跃的边界,必须跳一次):
      • jumps++(跳跃次数+1);
      • currentEnd = farthest(将下一跳的边界更新为当前能到达的最远位置);
      • 提前终止判断:若currentEnd >= n-1(已能到达终点,无需继续遍历),直接break;
  4. 返回结果:遍历完成后返回jumps,即为最小跳跃次数。
核心优化逻辑说明
  1. 时间复杂度最优:仅一次遍历数组(O(n)),且遍历范围缩小到n-2,每个元素仅被访问一次,耗时1ms击败99.63%用户;
  2. 空间复杂度极致:仅使用四个基础变量,空间复杂度O(1),无额外内存开销;
  3. 关键优化点解析
    • 遍历到n-2:最后一个下标n-1是目标,遍历到该位置无意义,减少一次无效循环;
    • 提前终止:当currentEnd >= n-1时直接break,避免后续无效遍历,进一步提升性能;
    • 跳跃时机精准:仅在到达当前边界时触发跳跃,确保每一次跳跃都是必要的,且选择最优目标;
  4. 内存表现优秀:内存消耗46.3MB击败92.74%用户,是因为无任何冗余变量,内存占用达到理论最优。
示例推演(以nums = [2,3,1,1,4]为例)
  • 初始状态:jumps=0,currentEnd=0,farthest=0;
  • i=0:farthest = max(0, 0+2)=2 → i==currentEnd(0==0)→ jumps=1,currentEnd=2 → 未到终点,继续;
  • i=1:farthest = max(2, 1+3)=4 → i≠currentEnd(1≠2),继续;
  • i=2:farthest = max(4, 2+1)=4 → i==currentEnd(2==2)→ jumps=2,currentEnd=4 → currentEnd≥4(n-1=4),break;
  • 返回jumps=2,与示例结果一致。
public int jump(int[] nums) {
            int n = nums.length;
            if (n <= 1) return 0; // 已经在终点

            int jumps = 0;          // 跳跃次数
            int currentEnd = 0;     // 当前这一跳能到达的最远边界
            int farthest = 0;       // 在当前覆盖范围内,下一跳能到达的最远位置

            // 遍历到 n-2 即可!因为到达 n-1 就结束了,不需要再跳
            for (int i = 0; i < n - 1; i++) {
                // 更新下一跳的最远可达位置
                farthest = Math.max(farthest, i + nums[i]);

                // 如果到达当前跳跃的边界,必须跳一次
                if (i == currentEnd) {
                    jumps++;
                    currentEnd = farthest;

                    // 提前终止:如果已经能到达或超过终点
                    if (currentEnd >= n - 1) {
                        break;
                    }
                }
            }
            return jumps;
        }

总结

  1. 该解法的核心是贪心策略的精准落地:通过“当前边界”和“下一跳最远位置”两个变量,确保每一次跳跃都选择最优路径,从而得到最小次数;
  2. 关键优化点:
    • 遍历范围限定到n-2,避免无效循环;
    • 到达边界才触发跳跃,确保跳跃次数最少;
    • 提前终止条件,减少不必要的遍历;
  3. 性能优势:时间复杂度O(n)适配题目10⁴级别的数据规模,是该问题的理论最优解,且空间复杂度O(1),在面试和工程中都是首选写法。