一、题目描述
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[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
- 题目保证可以到达
nums[n-1]
二、解题思路
-
初始化变量:我们需要三个变量来跟踪算法的状态。
currentEnd
表示当前我们已经到达的最远位置,初始值为 0,因为我们从数组的第一个元素开始。maxReach
用来记录在当前跳跃范围内能够到达的最远位置。step
用来记录到达数组末尾所需的最小跳跃次数,初始值为 0。 -
遍历数组:我们使用一个
for
循环来遍历数组nums
,直到i
达到数组的倒数第二个元素(即nums.length - 2
),因为在最坏的情况下,我们可能需要在每个元素上都进行一次跳跃。 -
更新最远距离:在每次循环中,我们更新
maxReach
为从当前位置i
出发能够到达的最远位置。这是通过遍历从i
到min(i + nums[i], currentEnd)
的所有位置来实现的,其中min(i + nums[i], currentEnd)
确保我们不会超出已经到达的最远位置currentEnd
。我们取这个范围内的最大值作为maxReach
。 -
跳跃决策:如果当前位置
i
已经等于currentEnd
,这意味着我们需要进行一次新的跳跃。我们更新currentEnd
为maxReach
,这表示我们已经到达了一个新的最远位置。同时,我们增加step
的值,记录下这次跳跃。 -
返回结果:当循环结束时,
step
变量包含了到达数组末尾所需的最小跳跃次数。我们返回这个值作为结果。
三、具体代码
class Solution {
public int jump(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int currentEnd = 0; // 当前能够到达的最远位置
int maxReach = 0; // 当前能够跳到的最远距离
int step = 0; // 需要的最小步数
for (int i = 0; i < nums.length - 1; i++) {
// 更新能够跳到的最远距离
maxReach = Math.max(maxReach, i + nums[i]);
// 如果当前位置已经超过了之前的最远位置,说明需要进行一次跳跃
if (i == currentEnd) {
currentEnd = maxReach; // 更新最远位置
step++; // 步数加一
}
}
return step;
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 该算法的主要操作是一个单层循环,它遍历了数组
nums
一次。 - 在每次循环中,有一个
Math.max
操作,这个操作的时间复杂度是 O(1)。 - 循环的每次迭代中,我们可能会更新
maxReach
,这个操作的时间复杂度也是 O(1)。 - 由于循环只执行了 n-1 次(其中 n 是数组
nums
的长度),所以总的时间复杂度是 O(n)。
2. 空间复杂度
- 除了输入数组
nums
外,算法使用了三个额外的变量currentEnd
、maxReach
和step
来存储状态。 - 这些变量的空间需求都是常数级别的,不依赖于输入数组的大小。
- 因此,空间复杂度是 O(1)。
五、总结知识点
-
数组的前提检查:在进行主要逻辑处理之前,代码首先检查输入数组
nums
是否为空或者长度为 0,如果是,则直接返回 0。这是一种常见的防御性编程实践,用于处理非法或异常输入。 -
循环控制:代码使用了一个
for
循环来遍历数组nums
,循环的范围直到nums.length - 1
,这是因为最后一次跳跃可以直接到达数组的最后一个元素,不需要再进行循环。 -
变量的作用域和初始化:代码中定义了三个变量
currentEnd
、maxReach
和step
,它们分别用于跟踪当前能够到达的最远位置、当前能够跳到的最远距离以及需要的最小步数。这些变量在方法的作用域内有效,并且在声明时进行了初始化。 -
贪心算法:这个问题的解决方案采用了贪心算法的思想。在每一步中,算法都会尝试找到能够到达的最远位置,并且只在必要时进行跳跃。这种策略是为了最小化总的跳跃次数。
-
条件判断和更新:在循环内部,通过
Math.max
函数来更新maxReach
,确保每次都是基于当前能够到达的最远距离进行决策。当当前索引i
达到currentEnd
时,意味着之前的跳跃范围已经遍历完毕,需要进行下一次跳跃,此时更新currentEnd
并增加step
。 -
算法结束条件:循环会在
i
达到nums.length - 1
时结束,这时currentEnd
至少等于nums.length - 1
,表示已经到达或超过了数组的最后一个位置。 -
返回值:方法的最后返回
step
,即到达数组末尾所需的最小跳跃次数。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。