文章目录
前言
本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。
一、题目
1、原题链接
2、题目描述
二、个人思路整理
1、思路分析
核心思路:维护最远可达位置
-
核心观察:
- 如果你能到达某个下标 i i i,那么从起点到 i i i 之间的所有位置你都能到达。
- 在下标 i i i 时,你能到达的最远位置为 i + nums i i + \text{nums}i i+numsi。
- 因此,我们只需要遍历数组,动态更新全局最远可达下标
max_reach。
-
状态转移与边界:
- 初始状态:
max_reach = 0。 - 遍历每个下标 i i i:
- 不可达判断:如果当前下标 i > max_reach i > \text{max\_reach} i>max_reach,说明前面的跳跃无论如何都够不到当前位置,后续也绝不可能到达,直接返回
false。 - 更新覆盖范围:更新
max_reach = max(max_reach, i + nums[i])。 - 提前终止(剪枝):如果此时
max_reach >= n - 1,说明已经可以到达或超过最后一个下标,直接返回true。
- 不可达判断:如果当前下标 i > max_reach i > \text{max\_reach} i>max_reach,说明前面的跳跃无论如何都够不到当前位置,后续也绝不可能到达,直接返回
- 如果能顺利遍历结束,说明可以到达终点,返回
true。
- 初始状态:
2、解题代码
cpp
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int max_reach = 0; // 记录当前跳跃到达的最远下标
for (int i = 0; i < n; i++) {
// 如果当前位置已经超过了之前能到达的最远距离,说明中间断档,无法到达此处
if (i > max_reach) {
return false;
}
// 贪心更新当前能够覆盖的最远下标:当前下标 + 该处最大跳跃步数
max_reach = max(max_reach, i + nums[i]);
// 剪枝优化: 如果最远可达位置已经覆盖或超过了终点下标,直接返回成功
if (max_reach >= n - 1) {
return true;
}
}
return true;
}
};
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),只需线性遍历一次数组。
- 空间复杂度: O ( 1 ) O(1) O(1),仅需常数级别的变量维护状态。
三、知识风暴
贪心算法(Greedy Algorithm) 是本题的核心算法思想。它通过每一步都做出当前看起来最优的选择,来逐步逼近全局最优解。对于跳跃游戏而言,我们不需要回溯所有可能的跳跃路径,只需在遍历过程中动态维护「当前能到达的最远位置」,即可判断能否抵达终点。理解贪心策略的正确性与边界处理,对掌握本题至关重要。
算法核心思想:
- 局部最优推导全局最优 :在每个下标 i i i,我们只关心「从当前位置能跳到的最远位置」 i + nums i i + \text{nums}i i+numsi,并不断更新全局最远可达下标
max_reach。只要max_reach能覆盖到终点,就一定存在一条可行路径。 - 不可达的判定 :如果遍历到某个下标 i i i 时,发现 i > max_reach i > \text{max\_reach} i>max_reach,说明前面的所有跳跃都无法触及当前位置,中间出现了「断档」,后续也绝不可能到达,直接返回
false。 - 与动态规划的区别:动态规划需要记录每个位置是否可达,并可能回溯多条路径;而贪心只维护一个最远可达边界,无需额外数组,空间复杂度更低。
常见对比:贪心 vs 动态规划
- 贪心算法 :时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)。只维护最远可达位置,思路简洁、效率最高,是本题的最优解。
- 动态规划 :时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)。需要记录每个下标是否可达,并逐一判断从前面的哪些位置能跳到当前位置,适合需要统计路径数量或具体路径的场景。
- 共同点:两者都能判断能否到达终点。区别在于贪心只关心「最远能到哪」,而动态规划关心「每个位置是否可达」。
"贪心"设计思想:
- 核心思想 :利用「可达区间是连续的」这一性质------如果你能到达下标 i i i,那么起点到 i i i 之间的所有位置都能到达。因此只需维护一个不断右移的右边界
max_reach,无需关心区间内部的具体跳法。 - 与本题的联系 :跳跃游戏只要求判断「能否到达终点」,不要求给出具体跳跃路径。因此我们无需记录每一步怎么跳,只需保证
max_reach始终覆盖当前遍历到的下标即可。 - 注意事项 :
max_reach的更新必须取「当前最远」与「从 i i i 出发能到的最远位置」的较大值,即max_reach = max(max_reach, i + nums[i]),不能直接覆盖为i + nums[i],否则可能丢失之前位置带来的更远覆盖。
使用要点:
- 循环遍历 :用
for (int i = 0; i < n; i++)顺序遍历每个下标,在循环内先判断是否可达,再更新最远覆盖。 - 不可达判断 :
if (i > max_reach) return false;必须放在更新之前,因为当前位置不可达时,后续位置更不可能到达。 - 剪枝优化 :每次更新后判断
if (max_reach >= n - 1) return true;,一旦覆盖终点即可提前结束,无需遍历完整个数组。 - 结果返回 :若循环正常结束(未提前返回),说明所有下标都在可达范围内,返回
true。
算法变体与扩展:
- 跳跃游戏(LeetCode 55):本题,贪心维护最远可达位置的标准应用。
- 跳跃游戏 II(LeetCode 45):在保证能到达终点的前提下,求最少跳跃次数。需要额外维护「当前步的覆盖边界」与「下一步的最远覆盖」,是贪心的进阶应用。
- 跳跃游戏 III(LeetCode 1306) :从起点出发,每次只能跳到
i + nums[i]或i - nums[i],且不能越界。由于跳跃方向不唯一,贪心失效,需改用 BFS 或 DFS 搜索。 - 跳跃游戏 IV(LeetCode 1345):数组中相同值的下标可以互相跳跃,求最少步数。需要结合哈希表分组 + BFS 求解,是图论建模的进阶应用。
相关 LeetCode 例题:
- 55. 跳跃游戏(贪心 + 维护最远可达位置)
- 45. 跳跃游戏 II(贪心 + 最少跳跃次数)
- 1306. 跳跃游戏 III(BFS/DFS + 可达性搜索)
- 1345. 跳跃游戏 IV(哈希表 + BFS 最短路)
