📖 第66课:跳跃游戏

4 阅读22分钟

想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。

📖 第66课:跳跃游戏

模块:贪心算法 | 难度:Medium ⭐⭐⭐ LeetCode 链接:leetcode.cn/problems/ju… 前置知识:数组遍历、贪心思想 预计学习时间:20分钟


🎯 题目描述

给定一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。

示例:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步从下标 0 到 1,然后跳 3 步到最后一个下标。

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标 3。但该下标的最大跳跃长度是 0,所以永远不可能到达最后一个下标。

约束条件:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 10^5
  • nums[i] 表示最大跳跃长度,可以跳 0 到 nums[i] 之间的任意步数

🧪 边界用例(面试必考)

用例类型输入期望输出考察点
最小输入nums=[0]true已在终点
只有一步nums=[1,0]true恰好能到
中间有0nums=[2,0,0]true能跨越0
0陷阱nums=[3,2,1,0,4]false被0阻断
全是大数nums=[5,9,3,2,1,0]true任意跳都能到
大规模n=10^4-必须O(n)算法

💡 思路引导

生活化比喻

想象你在玩一个"跳格子"游戏,站在起点的格子上,每个格子上都写着一个数字,表示你在这个格子最多能向前跳几步。你的目标是跳到最后一个格子。

🐌 笨办法:尝试所有可能的跳跃路径,用递归或BFS遍历所有可能性,看有没有一条路径能到达终点。这就像一个"决策树",每个格子都有多个选择,复杂度会爆炸增长(最坏O(2^n))。

🚀 聪明办法:不用关心具体怎么跳,只需要维护"当前能到达的最远位置"。一边向前走,一边更新最远位置。如果最远位置覆盖了终点,就一定能到达;如果走到某个位置发现"最远位置都到不了这里",说明被前面的0阻断了,无法到达终点。

关键洞察

核心思想:贪心地维护"最远可达位置",不需要知道具体路径

这是贪心算法的精髓——我们不需要求出"怎么跳才能到终点",只需要判断"能不能到终点"。维护一个变量 max_reach 表示当前能到达的最远位置,遍历过程中不断更新它。


🧠 解题思维链

这一节模拟你在面试中"从零开始思考"的过程。

Step 1:理解题目 → 锁定输入输出

  • 输入:非负整数数组 nums,长度 1 到 10^4
  • 输出:布尔值 true/false,表示能否到达最后一个位置
  • 限制:nums[i] 是最大跳跃长度,可以选择跳 1 到 nums[i] 步

Step 2:先想笨办法(回溯/BFS)

最直接的想法:用回溯或BFS遍历所有可能的跳跃路径,看有没有一条能到达终点。

  • 时间复杂度:O(2^n) 或更差 — 每个位置都有多种跳跃选择,形成指数级决策树
  • 瓶颈在哪:尝试了很多无用的路径,存在大量重复计算

Step 3:瓶颈分析 → 优化方向

分析笨办法的问题:

  • 核心问题:题目只问"能否到达",不需要知道"怎么到达",但笨办法在寻找具体路径
  • 优化思路:能不能只关注"能到达的最远位置",而不关心具体跳跃路径?

Step 4:选择武器

  • 选用:贪心算法 + 最远可达位置维护
  • 理由:维护一个全局最值(max_reach),每次局部决策(更新最远位置)都不影响最终结果,符合贪心的无后效性

🔑 模式识别提示:当题目问"能否到达/能否完成",不要求具体方案时,优先考虑"贪心维护可达范围"


🔑 解法一:动态规划(记忆化递归)

思路

定义 dp[i] 表示"能否从位置 i 到达终点"。从后往前计算,如果位置 i 能跳到任意一个 dp[j]=true 的位置 j,则 dp[i]=true。

图解过程

nums = [2, 3, 1, 1, 4]
索引:   0  1  2  3  4

从后往前计算 dp:
dp[4] = true (已在终点)

dp[3]:位置3能跳1步,能到达位置4(dp[4]=true) → dp[3]=true

dp[2]:位置2能跳1步,能到达位置3(dp[3]=true) → dp[2]=true

dp[1]:位置1能跳3步,能到达位置2/3/4(都是true) → dp[1]=true

dp[0]:位置0能跳2步,能到达位置1/2(都是true) → dp[0]=true

答案:dp[0]=true

Python代码

from typing import List


def canJump_dp(nums: List[int]) -> bool:
    """
    解法一:动态规划(记忆化递归)
    思路:dp[i] 表示能否从位置 i 到达终点
    """
    n = len(nums)
    # dp[i] 表示位置 i 能否到达终点
    dp = [False] * n
    dp[n - 1] = True  # 终点位置

    # 从倒数第二个位置开始向前遍历
    for i in range(n - 2, -1, -1):
        # 从位置 i 尝试跳跃 1 到 nums[i] 步
        max_jump = min(nums[i], n - 1 - i)  # 最多跳到终点
        for j in range(1, max_jump + 1):
            if dp[i + j]:
                dp[i] = True
                break  # 找到一条可达路径即可

    return dp[0]


# ✅ 测试
print(canJump_dp([2,3,1,1,4]))  # 期望输出:true
print(canJump_dp([3,2,1,0,4]))  # 期望输出:false

复杂度分析

  • 时间复杂度:O(n²) — 外层循环 n 次,内层循环最多 n 次(当 nums[i] 很大时)
    • 具体地说:如果输入规模 n=1000,最坏需要 1000×1000 = 100万次操作
  • 空间复杂度:O(n) — dp 数组

优缺点

  • ✅ 思路直观,容易从递归推导
  • ✅ 为理解问题打基础
  • ❌ 时间复杂度O(n²)不够优,大数据会超时
  • ❌ 有更简洁的贪心解法

🏆 解法二:贪心算法 - 维护最远可达位置(最优解)

优化思路

从解法一的痛点出发:我们不需要知道具体跳跃路径,只需要判断能否到达。核心观察:如果位置 i 可达,那么位置 i+1 到 i+nums[i] 之间的所有位置都可达。

💡 关键想法:维护一个变量 max_reach 表示"当前能到达的最远位置"。遍历数组,对于每个位置 i:

  1. 如果 i > max_reach,说明当前位置不可达,返回 false
  2. 否则,更新 max_reach = max(max_reach, i + nums[i])
  3. 如果 max_reach >= n-1,说明能到达终点,返回 true

贪心策略的正确性证明:

  • 局部最优:每次都尽可能扩展"最远可达位置"
  • 全局最优:如果存在任意一条路径能到达终点,那么贪心策略一定能发现(因为它覆盖了所有可能的位置)
  • 无后效性:当前位置能到达哪里,不影响之前位置的可达性

图解过程

示例1:nums = [2, 3, 1, 1, 4]
        索引: 0  1  2  3  4

初始化: max_reach = 0 (从位置0开始)

位置0(值2):
  i=0 <= max_reach=0 ✓ (当前位置可达)
  更新: max_reach = max(0, 0+2) = 2
  状态: [可达范围 0-2]

  0  1  2  3  4
  ●--●--●        ← 从0能跳到0,1,2

位置1(值3):
  i=1 <= max_reach=2 ✓ (当前位置可达)
  更新: max_reach = max(2, 1+3) = 4 ← 能跳到终点!
  状态: [可达范围 0-4]

  0  1  2  3  4
  ●--●--●--●--●  ← 从1能跳到1,2,3,4

此时 max_reach=4 >= 终点索引4,返回 true


示例2:nums = [3, 2, 1, 0, 4]
        索引: 0  1  2  3  4

位置0(值3):
  max_reach = max(0, 0+3) = 3

  0  1  2  3  4
  ●--●--●--●     ← 从0能跳到0,1,2,3

位置1(值2):
  max_reach = max(3, 1+2) = 3 (保持)

位置2(值1):
  max_reach = max(3, 2+1) = 3 (保持)

位置3(值0):
  max_reach = max(3, 3+0) = 3 (保持)
  ← 到此为止最远只能到位置3

位置4:
  i=4 > max_reach=3 ✗ (位置4不可达!)

  0  1  2  3  4
  ●--●--●--●  X  ← 位置4在可达范围之外

  返回 false


可视化"能量传递"的过程:
nums = [2, 3, 1, 1, 4]

步骤1: 从0出发,能量可达位置0~2
  [ 2  3  1  1  4 ]
    ↑=====↑
    起点  能量边界

步骤2: 位置1有能量3,扩展边界到1+3=4
  [ 2  3  1  1  4 ]
    ↑  ↑=========↑
    起点 新能量  新边界(覆盖终点!)

结论: 能量波能覆盖终点,返回 true

Python代码

from typing import List


def canJump(nums: List[int]) -> bool:
    """
    解法二:贪心算法 - 维护最远可达位置
    思路:遍历数组,维护当前能到达的最远位置
    """
    max_reach = 0  # 当前能到达的最远位置

    for i in range(len(nums)):
        # 如果当前位置不可达,直接返回 false
        if i > max_reach:
            return False

        # 更新最远可达位置
        max_reach = max(max_reach, i + nums[i])

        # 如果最远位置已覆盖终点,提前返回 true
        if max_reach >= len(nums) - 1:
            return True

    return True  # 遍历完成,说明所有位置都可达


# ✅ 测试
print(canJump([2,3,1,1,4]))    # 期望输出:true
print(canJump([3,2,1,0,4]))    # 期望输出:false
print(canJump([0]))            # 期望输出:true (已在终点)
print(canJump([1,1,1,0]))      # 期望输出:true
print(canJump([1,0,1,0]))      # 期望输出:false

复杂度分析

  • 时间复杂度:O(n) — 只遍历一次数组,每个元素访问一次
    • 具体地说:如果输入规模 n=10,000,只需要 10,000 次操作,比DP法快了1000倍!
  • 空间复杂度:O(1) — 只用了一个变量 max_reach

为什么O(n)是最优的?

  • 理论下界:至少要看一遍数组才能知道每个位置的跳跃能力,所以 O(n) 是理论最优
  • 这个解法已经达到了理论下界,时间空间都无法再优化

⚡ 解法三:贪心算法 - 从后往前找(变体)

优化思路

换一个角度思考:从终点开始倒推,找最左边能一步跳到终点的位置,然后继续往前找能跳到这个位置的位置,以此类推。如果最终能追溯到起点,说明可达。

💡 核心想法:维护一个"当前目标位置",初始为终点。从右往左遍历,如果当前位置能跳到目标位置,就将目标位置更新为当前位置。最后检查目标位置是否为起点。

Python代码

from typing import List


def canJump_backward(nums: List[int]) -> bool:
    """
    解法三:贪心算法 - 从后往前找
    思路:从终点倒推,找能一步到达的最左位置
    """
    n = len(nums)
    target = n - 1  # 当前需要到达的目标位置

    # 从倒数第二个位置开始向前遍历
    for i in range(n - 2, -1, -1):
        # 如果位置 i 能跳到目标位置,更新目标为位置 i
        if i + nums[i] >= target:
            target = i

    # 最终目标是否回到了起点
    return target == 0


# ✅ 测试
print(canJump_backward([2,3,1,1,4]))  # 期望输出:true
print(canJump_backward([3,2,1,0,4]))  # 期望输出:false

图解过程

nums = [2, 3, 1, 1, 4]
索引:   0  1  2  3  4

初始: target = 4 (终点)

位置3(值1): 3+1=4 >= target=4 ✓ → target更新为3
位置2(值1): 2+1=3 >= target=3 ✓ → target更新为2
位置1(值3): 1+3=4 >= target=2 ✓ → target更新为1
位置0(值2): 0+2=2 >= target=1 ✓ → target更新为0

最终 target=0,返回 true


nums = [3, 2, 1, 0, 4]
索引:   0  1  2  3  4

初始: target = 4 (终点)

位置3(值0): 3+0=3 < target=4 ✗ → target保持4
位置2(值1): 2+1=3 < target=4 ✗ → target保持4
位置1(值2): 1+2=3 < target=4 ✗ → target保持4
位置0(值3): 0+3=3 < target=4 ✗ → target保持4

最终 target=4 != 0,返回 false

复杂度分析

  • 时间复杂度:O(n) — 从后往前遍历一次
  • 空间复杂度:O(1) — 只用了一个变量 target

优缺点

  • ✅ 逻辑清晰,从结果倒推原因
  • ✅ 时间空间都是最优
  • ✅ 不需要提前终止循环,代码更简洁
  • ⚠️ 需要完整遍历,不能像解法二那样提前返回

🐍 Pythonic 写法

利用 Python 的语法糖,可以写得更简洁:

def canJump_pythonic(nums: List[int]) -> bool:
    """Pythonic 简洁写法"""
    max_reach = 0
    for i, jump in enumerate(nums):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + jump)
        if max_reach >= len(nums) - 1:
            return True
    return True

一行流写法(不推荐面试用,但可以炫技):

def canJump_oneliner(nums: List[int]) -> bool:
    """一行流(仅供欣赏)"""
    return reduce(
        lambda reach, pos_jump: max(reach, pos_jump[0] + pos_jump[1]) if pos_jump[0] <= reach else reach,
        enumerate(nums),
        0
    ) >= len(nums) - 1

⚠️ 面试建议:优先写清晰版本(解法二)展示思路,强调贪心策略的正确性证明。代码的可读性比简洁性更重要。


📊 解法对比

维度解法一:动态规划🏆 解法二:贪心(正向)(最优)解法三:贪心(反向)
时间复杂度O(n²)O(n) ← 时间最优O(n)
空间复杂度O(n)O(1) ← 空间最优O(1)
代码难度中等简单简单
面试推荐⭐⭐⭐ ← 首选⭐⭐
适用场景理解问题用面试首选,最优解换个角度思考,同样优秀
可提前返回是(覆盖终点即返回)否(需完整遍历)

为什么解法二是最优解?

  1. 时间复杂度O(n)已经是理论最优:必须至少看一遍所有位置的跳跃能力
  2. 空间复杂度O(1)已经是最优:只用了一个变量,无法更省
  3. 能提前返回:一旦最远位置覆盖终点,立即返回,不需要遍历完整个数组
  4. 贪心策略直观:完美体现"局部最优→全局最优"的思想,易于理解和证明

面试建议:

  1. 先用30秒口述动态规划思路,表明你能想到基本解法
  2. 立即优化到🏆最优解(贪心正向遍历),展示优化能力
  3. 重点讲解贪心策略:"维护最远可达位置,如果当前位置不可达就说明被前面的0阻断了"
  4. 强调为什么正确:如果存在任意一条路径能到终点,贪心策略一定能发现,因为它覆盖了所有可能的中间位置
  5. 可以提及解法三作为"不同角度思考"的加分项
  6. 手动测试边界用例(单个元素、中间有0),展示对解法的深入理解

🎤 面试现场

模拟面试中的完整对话流程,帮你练习"边想边说"。

面试官:请你判断给定的跳跃数组能否到达最后一个位置。

:(审题30秒)好的,这道题给定一个数组,每个位置表示最大跳跃长度,要判断能否从起点跳到终点。让我先想一下...

我的第一个想法是用动态规划:定义 dp[i] 表示位置 i 能否到达终点,从后往前计算。但这样时间复杂度是 O(n²),因为每个位置要尝试所有可能的跳跃。

更好的方法是用贪心算法:我们不需要知道具体怎么跳,只需要维护"当前能到达的最远位置"。遍历数组,如果当前位置 i 在可达范围内(i <= max_reach),就更新最远位置为 max(max_reach, i + nums[i])。如果最远位置覆盖了终点,返回 true;如果遍历到某个位置发现它在可达范围外,返回 false。

面试官:很好,为什么这个贪心策略是正确的?

:因为如果存在任意一条路径能到达终点,那么这条路径上的每个位置都必须是"可达的"。我们的贪心策略维护的 max_reach 覆盖了所有可能通过任何路径到达的位置。换句话说:

  • 如果终点可达,一定存在某条路径,这条路径上的所有点都在某个时刻被 max_reach 覆盖
  • 如果贪心策略发现终点不在 max_reach 范围内,说明不存在任何路径能到达

这符合贪心算法的"局部最优→全局最优"特性,而且具有无后效性。

面试官:请写一下代码。

:(边写边说)我用一个变量 max_reach 记录最远可达位置,初始为 0。遍历数组,对于每个位置 i:首先检查它是否可达(i <= max_reach),如果不可达直接返回 false;否则更新 max_reach 为 max(max_reach, i+nums[i])。如果 max_reach 覆盖终点,提前返回 true。

def canJump(nums):
    max_reach = 0
    for i in range(len(nums)):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + nums[i])
        if max_reach >= len(nums) - 1:
            return True
    return True

面试官:测试一下?

:用示例 [2,3,1,1,4] 走一遍:

  • i=0,nums[0]=2: max_reach=max(0,0+2)=2,覆盖位置0~2
  • i=1,nums[1]=3: i=1<=2可达,max_reach=max(2,1+3)=4,覆盖位置0~4
  • max_reach=4 >= 终点4,返回 true ✓

再测边界情况 [3,2,1,0,4]:

  • i=0: max_reach=3
  • i=1: max_reach=max(3,1+2)=3
  • i=2: max_reach=max(3,2+1)=3
  • i=3: max_reach=max(3,3+0)=3
  • i=4: i=4 > max_reach=3,返回 false ✓ (位置3的0形成了陷阱)

高频追问

追问应答策略
"能否返回最少跳跃次数?"那就是 LeetCode 45。用贪心算法:维护当前跳跃的边界,每次到达边界时跳跃次数+1,并更新边界为之前能到达的最远位置。
"如果每个位置是固定跳跃长度呢?"问题会简化,可以用数学方法:检查是否存在位置 i 使得 i % nums[i] != 0 且后续无法跨越。
"从后往前跳呢?"同样可以用贪心:从终点开始,找最左边能一步跳到当前目标的位置,依次往前推,看能否回到起点(解法三)。
"如果数组特别大怎么办?"O(n)已经是线性时间,可以流式处理。但空间已经是O(1)最优,无法再优化。
"能否打印出具体路径?"需要额外维护一个 parent 数组记录跳跃来源,在找到终点后回溯打印。空间复杂度变为 O(n)。

🎓 知识点总结

Python技巧卡片 🐍

# 技巧1:enumerate 同时获取索引和值
for i, jump in enumerate(nums):
    max_reach = max(max_reach, i + jump)

# 技巧2:提前返回优化(短路)
if max_reach >= len(nums) - 1:
    return True  # 不需要继续遍历

# 技巧3:range 倒序遍历
for i in range(n - 2, -1, -1):  # 从 n-2 到 0
    # 从后往前处理

# 技巧4:三元表达式简化判断
result = True if i <= max_reach else False
# 等价于: result = (i <= max_reach)

# 技巧5:min 限制跳跃范围
max_jump = min(nums[i], n - 1 - i)  # 最多跳到终点,不能越界

💡 底层原理(选读)

贪心算法的正确性证明(反证法):

假设贪心算法返回 false,即存在某个位置 k,使得 k > max_reach。

  • 这意味着从起点出发,无论怎么跳,都无法到达位置 k
  • 如果终点在位置 k 之后,显然也无法到达,贪心算法正确返回 false

假设实际上存在一条路径能到达终点:

  • 这条路径必须经过位置 k(因为终点在 k 之后)
  • 但我们已证明 k 不可达,矛盾!
  • 所以假设不成立,贪心算法正确

贪心 vs 动态规划 vs 回溯:

  • 回溯:枚举所有可能路径,时间复杂度指数级 O(2^n),只适合小规模
  • 动态规划:存储子问题结果,避免重复计算,时间 O(n²),空间 O(n)
  • 贪心:直接做局部最优决策,不回溯不存储,时间 O(n),空间 O(1) ← 最优

这道题能用贪心的关键:问题具有最优子结构,且局部最优不会破坏全局最优

算法模式卡片 📐

  • 模式名称:贪心算法 — 维护最远可达位置
  • 适用条件:需要判断"能否到达某个目标",不要求具体路径;每一步的局部最优决策不影响全局
  • 识别关键词:"能否到达"、"最远位置"、"跳跃游戏"、"覆盖范围"
  • 模板代码:
def greedy_reach(nums):
    """贪心维护最远可达位置模板"""
    max_reach = 0  # 当前最远可达位置
    target = len(nums) - 1  # 目标位置

    for i in range(len(nums)):
        # 检查当前位置是否可达
        if i > max_reach:
            return False  # 不可达,失败

        # 更新最远可达位置
        max_reach = max(max_reach, i + nums[i])

        # 检查是否已覆盖目标
        if max_reach >= target:
            return True  # 提前返回

    return max_reach >= target

同类型题目:

  • LeetCode 45. 跳跃游戏 II(求最少跳跃次数)
  • LeetCode 1306. 跳跃游戏 III(可以前后跳)
  • LeetCode 1345. 跳跃游戏 IV(通过相同值跳跃)
  • LeetCode 134. 加油站(维护油量可达性)

易错点 ⚠️

  1. 错误:忘记检查当前位置是否可达

    # ❌ 错误写法
    for i in range(len(nums)):
        max_reach = max(max_reach, i + nums[i])  # 没检查 i 是否可达
    

    问题:如果位置 i 本身不可达(i > max_reach),就不应该用它更新 max_reach 正确做法:先检查 if i > max_reach: return False

  2. 错误:边界条件没处理

    # ❌ 错误写法:没考虑只有一个元素的情况
    if len(nums) == 0:
        return False  # 应该返回 true,因为已在终点
    

    正确做法:单个元素返回 true,或者让逻辑自然处理(max_reach=0 >= target=0)

  3. 错误:提前返回条件写错

    # ❌ 错误写法
    if max_reach > len(nums):  # 应该是 >= len(nums) - 1
        return True
    

    问题:max_reach 可以等于 len(nums)-1 就算到达终点 正确做法:if max_reach >= len(nums) - 1

  4. 错误:混淆"最大跳跃长度"和"必须跳跃长度"

    • nums[i] 是最大跳跃长度,可以跳 0 到 nums[i] 之间的任意步数
    • 不是"必须跳 nums[i] 步"

🏗️ 工程实战(选读)

这个算法思想在真实项目中的应用,让你知道"学了有什么用"。

  • 场景1:网络路由选择

    • 路由器转发数据包时,每个路由器知道自己能到达的下一跳范围
    • 判断数据包能否到达目标,类似跳跃游戏的可达性判断
  • 场景2:任务调度系统

    • 分布式系统中,每个节点有一定的处理能力(类比跳跃长度)
    • 判断任务队列能否被完全处理完,类似判断能否到达终点
  • 场景3:游戏关卡设计

    • 平台跳跃游戏中,设计师需要验证关卡的可通过性
    • 用贪心算法快速检测玩家能否从起点跳到终点
  • 场景4:供应链物流

    • 每个仓库有一定的配送半径(类比跳跃长度)
    • 判断商品能否从起始仓库送达目标城市,覆盖范围扩展问题

🏋️ 举一反三

完成本课后,试试这些同类题目来巩固知识:

题目难度相关知识点提示
LeetCode 45. 跳跃游戏 IIMedium贪心(最少跳跃次数)维护当前跳跃的边界和下次能到的最远位置
LeetCode 1306. 跳跃游戏 IIIMediumBFS/DFS(可前后跳)值为0的位置是目标,可以向左或向右跳
LeetCode 1345. 跳跃游戏 IVHardBFS+哈希表(相同值跳跃)相同值的位置之间可以直接跳,用哈希表优化
LeetCode 134. 加油站Medium贪心(油量可达性)维护剩余油量,类似维护可达范围
LeetCode 435. 无重叠区间Medium贪心(区间调度)按结束时间排序,贪心选择不重叠的最多区间

📝 课后小测

试试这道进阶题,不要看答案,自己先想5分钟!

题目:给定跳跃数组 nums,返回到达最后一个位置的最少跳跃次数。假设你总是可以到达最后一个位置。

示例:nums = [2,3,1,1,4],输出:2(跳1步到下标1,然后跳3步到最后)

💡 提示(实在想不出来再点开)

用贪心算法:维护两个变量,current_end 表示当前跳跃能到达的边界,farthest 表示下一跳能到达的最远位置。当遍历到 current_end 时,说明必须再跳一次了,更新 current_end 为 farthest,跳跃次数加1。

✅ 参考答案
def jump(nums: List[int]) -> int:
    """
    跳跃游戏 II — 最少跳跃次数(贪心算法)
    思路:维护当前跳跃边界和下次最远可达位置
    """
    if len(nums) <= 1:
        return 0

    jumps = 0           # 跳跃次数
    current_end = 0     # 当前跳跃能到达的最远位置
    farthest = 0        # 下一跳能到达的最远位置

    # 遍历到倒数第二个位置(最后一个位置不需要跳)
    for i in range(len(nums) - 1):
        # 更新下一跳的最远位置
        farthest = max(farthest, i + nums[i])

        # 到达当前跳跃的边界,必须再跳一次
        if i == current_end:
            jumps += 1
            current_end = farthest  # 更新边界

            # 如果新边界已覆盖终点,可以提前返回
            if current_end >= len(nums) - 1:
                break

    return jumps

# 测试
print(jump([2,3,1,1,4]))  # 输出:2
print(jump([2,3,0,1,4]))  # 输出:2

核心思路:

  • 贪心策略:每次跳跃都选择能到达的位置中,"下一跳能到达最远"的那个位置
  • 边界触发:当遍历到当前跳跃的边界时,说明"必须再跳一次了",此时更新边界并增加跳跃次数
  • 时间复杂度:O(n),只遍历一次数组
  • 空间复杂度:O(1),只用了几个变量

这是贪心算法的经典应用,体现了"在当前能到达的范围内,选择下一跳最优的位置"的策略。


如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。