想系统提升编程能力、查看更完整的学习路线,欢迎访问 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 | 恰好能到 |
| 中间有0 | nums=[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:
- 如果 i > max_reach,说明当前位置不可达,返回 false
- 否则,更新 max_reach = max(max_reach, i + nums[i])
- 如果 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) |
| 代码难度 | 中等 | 简单 | 简单 |
| 面试推荐 | ⭐ | ⭐⭐⭐ ← 首选 | ⭐⭐ |
| 适用场景 | 理解问题用 | 面试首选,最优解 | 换个角度思考,同样优秀 |
| 可提前返回 | 否 | 是(覆盖终点即返回) | 否(需完整遍历) |
为什么解法二是最优解?
- 时间复杂度O(n)已经是理论最优:必须至少看一遍所有位置的跳跃能力
- 空间复杂度O(1)已经是最优:只用了一个变量,无法更省
- 能提前返回:一旦最远位置覆盖终点,立即返回,不需要遍历完整个数组
- 贪心策略直观:完美体现"局部最优→全局最优"的思想,易于理解和证明
面试建议:
- 先用30秒口述动态规划思路,表明你能想到基本解法
- 立即优化到🏆最优解(贪心正向遍历),展示优化能力
- 重点讲解贪心策略:"维护最远可达位置,如果当前位置不可达就说明被前面的0阻断了"
- 强调为什么正确:如果存在任意一条路径能到终点,贪心策略一定能发现,因为它覆盖了所有可能的中间位置
- 可以提及解法三作为"不同角度思考"的加分项
- 手动测试边界用例(单个元素、中间有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. 加油站(维护油量可达性)
易错点 ⚠️
-
错误:忘记检查当前位置是否可达
# ❌ 错误写法 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 -
错误:边界条件没处理
# ❌ 错误写法:没考虑只有一个元素的情况 if len(nums) == 0: return False # 应该返回 true,因为已在终点正确做法:单个元素返回 true,或者让逻辑自然处理(max_reach=0 >= target=0)
-
错误:提前返回条件写错
# ❌ 错误写法 if max_reach > len(nums): # 应该是 >= len(nums) - 1 return True问题:max_reach 可以等于 len(nums)-1 就算到达终点 正确做法:
if max_reach >= len(nums) - 1 -
错误:混淆"最大跳跃长度"和"必须跳跃长度"
- nums[i] 是最大跳跃长度,可以跳 0 到 nums[i] 之间的任意步数
- 不是"必须跳 nums[i] 步"
🏗️ 工程实战(选读)
这个算法思想在真实项目中的应用,让你知道"学了有什么用"。
-
场景1:网络路由选择
- 路由器转发数据包时,每个路由器知道自己能到达的下一跳范围
- 判断数据包能否到达目标,类似跳跃游戏的可达性判断
-
场景2:任务调度系统
- 分布式系统中,每个节点有一定的处理能力(类比跳跃长度)
- 判断任务队列能否被完全处理完,类似判断能否到达终点
-
场景3:游戏关卡设计
- 平台跳跃游戏中,设计师需要验证关卡的可通过性
- 用贪心算法快速检测玩家能否从起点跳到终点
-
场景4:供应链物流
- 每个仓库有一定的配送半径(类比跳跃长度)
- 判断商品能否从起始仓库送达目标城市,覆盖范围扩展问题
🏋️ 举一反三
完成本课后,试试这些同类题目来巩固知识:
| 题目 | 难度 | 相关知识点 | 提示 |
|---|---|---|---|
| LeetCode 45. 跳跃游戏 II | Medium | 贪心(最少跳跃次数) | 维护当前跳跃的边界和下次能到的最远位置 |
| LeetCode 1306. 跳跃游戏 III | Medium | BFS/DFS(可前后跳) | 值为0的位置是目标,可以向左或向右跳 |
| LeetCode 1345. 跳跃游戏 IV | Hard | BFS+哈希表(相同值跳跃) | 相同值的位置之间可以直接跳,用哈希表优化 |
| 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 学习资料都在这里,后续复习和拓展会更省时间。