【Leetcode hot 100】45.跳跃游戏Ⅱ
问题链接
45.跳跃游戏Ⅱ
问题描述
给定一个长度为 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 <= 10^4
0 <= nums[i] <= 1000
- 题目保证可以到达
n - 1
问题解答
贪心算法(最优解)
思路分析
贪心策略的核心逻辑:
- 定义关键变量:
currentEnd
:当前跳跃的“边界”(跳完当前这一步后,能到达的最远位置)。farthest
:在当前跳跃的覆盖范围内,所有位置能到达的最大距离(决定下一步的边界)。jumps
:记录跳跃次数。
- 遍历数组:
- 遍历过程中,不断更新
farthest
(即Math.max(farthest, i + nums[i])
,i + nums[i]
表示从位置i
能跳到的最远位置)。 - 当遍历到
currentEnd
时,说明当前跳跃的覆盖范围已用尽,必须进行一次新的跳跃:- 跳跃次数
jumps
加1。 - 将
currentEnd
更新为farthest
(新的跳跃边界)。 - 提前终止:若
currentEnd
已覆盖终点(currentEnd >= nums.length - 1
),直接跳出循环(无需继续遍历)。
- 跳跃次数
- 遍历过程中,不断更新
代码实现
class Solution {public int jump(int[] nums) {// 特殊情况:数组长度为1,无需跳跃if (nums.length == 1) {return 0;}int currentEnd = 0; // 当前跳跃的边界int farthest = 0; // 当前覆盖范围内能到达的最远距离int jumps = 0; // 跳跃次数// 遍历到倒数第二个位置即可(最后一个位置是终点,无需处理)for (int i = 0; i < nums.length - 1; i++) {// 更新当前覆盖范围内的最远距离farthest = Math.max(farthest, i + nums[i]);// 到达当前跳跃的边界,必须跳一次if (i == currentEnd) {jumps++;currentEnd = farthest; // 更新新的跳跃边界// 提前终止:新边界已覆盖终点if (currentEnd >= nums.length - 1) {break;}}}return jumps;}
}
复杂度分析
- 时间复杂度:
O(n)
,仅遍历数组一次,每个元素处理一次。 - 空间复杂度:
O(1)
,仅使用3个额外变量,无额外空间开销。
动态规划(辅助理解)
若对贪心策略不熟悉,可先通过动态规划理解“最少跳跃次数”的推导过程。动态规划的核心是“记录每个位置到终点的最少跳跃次数”,但效率低于贪心算法。
思路分析
- 定义DP数组:
dp[i]
表示从位置i
到达终点(nums.length - 1
)的最少跳跃次数。 - 初始化:
- 终点位置
dp[nums.length - 1] = 0
(无需跳跃)。 - 其他位置初始化为
Integer.MAX_VALUE
(表示初始状态下无法到达终点,后续更新)。
- 终点位置
- 逆序推导:
- 从倒数第二个位置开始,逆序遍历每个位置
i
。 - 对每个位置
i
,遍历其能覆盖的所有位置j
(j
的范围:i + 1
到Math.min(i + nums[i], nums.length - 1)
)。 - 若
dp[j]
不为Integer.MAX_VALUE
,则dp[i] = Math.min(dp[i], dp[j] + 1)
(从i
跳到j
,再从j
到终点,总次数+1)。
- 从倒数第二个位置开始,逆序遍历每个位置
代码实现
class Solution {public int jump(int[] nums) {int n = nums.length;// dp[i]:从位置i到终点的最少跳跃次数int[] dp = new int[n];// 初始化:除终点外,其他位置初始化为“不可达”for (int i = 0; i < n - 1; i++) {dp[i] = Integer.MAX_VALUE;}dp[n - 1] = 0; // 终点无需跳跃// 逆序遍历:从倒数第二个位置开始推导for (int i = n - 2; i >= 0; i--) {// 遍历位置i能覆盖的所有位置jint maxJump = Math.min(i + nums[i], n - 1); // 避免越界for (int j = i + 1; j <= maxJump; j++) {// 若j可达终点,则更新dp[i]if (dp[j] != Integer.MAX_VALUE) {dp[i] = Math.min(dp[i], dp[j] + 1);}}}return dp[0]; // 从起点到终点的最少跳跃次数}
}
复杂度分析
- 时间复杂度:
O(n²)
,最坏情况下每个位置需遍历其覆盖的所有位置(如数组全为n-1
时)。 - 空间复杂度:
O(n)
,需额外存储dp
数组。
两种解法对比
解法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
贪心算法 | O(n) | O(1) | 追求高效,数组规模大(如10⁴ ) |
动态规划 | O(n²) | O(n) | 理解“最少跳跃次数”的推导逻辑,小规模数组 |