力扣最热一百题——跳跃游戏II
目录
题目链接:45. 跳跃游戏 II - 力扣(LeetCode)
题目描述
解法一:动态规划
关键点
Java写法:
C++写法:
运行时间
时间复杂度和空间复杂度
解法二:贪心算法
Java写法:
C++写法:
运行时间
时间复杂度和空间复杂度
时间复杂度
空间复杂度
总结
总结
题目链接:45. 跳跃游戏 II - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给定一个长度为 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 <=
0 <= nums[i] <= 1000
- 题目保证可以到达
nums[n-1]
解法一:动态规划
-
初始化:
- 首先确定输入数组的长度
len
。 - 创建一个一维数组
dp
来存储到达每个位置所需的最小跳跃次数,初始时将dp[0]
设置为 0(因为起点不需要跳跃),其余值设置为 0 或者默认值。
- 首先确定输入数组的长度
-
动态规划填充
dp
数组:- 从索引 1 开始遍历到数组的末尾(即外层循环)。
- 对于每一个位置
i
,尝试从前一个位置(j
,范围是从 0 到i-1
)跳过来,并检查是否可以从位置j
跳跃到位置i
(条件:j + nums[j] >= i
)。 - 如果可以从
j
跳到i
,并且这种跳法所需跳跃次数比目前记录的要少(即dp[j] + 1 < minStep
),则更新minStep
为dp[j] + 1
。 - 外层循环每次结束时,将
minStep
的值赋给dp[i]
,表示到达位置i
所需的最小跳跃次数。
-
返回结果:
- 最后返回
dp
数组的最后一个元素,即到达数组最后一个位置所需的最小跳跃次数。
- 最后返回
关键点
- 初始化和边界情况处理:注意到对于起始位置
dp[0] = 0
,因为从起点开始不需要任何跳跃。此外,minStep
初始化为一个较大的值(如 10001),以确保任意可行路径都会被选择作为更优解。
Java写法:
class Solution {
public int jump(int[] nums) {
int len = nums.length;
// 定义出DP数组
// 对应位置的值表示,跳跃到当前位置所需的最小跳跃次数
int[] dp = new int[len];
// 初始化第一位
dp[0] = 0;
for(int i = 1;i < len; i++){
// 这里找到i这个位置所需的最少次数(为什么初始化为10001看题目提示)
int minStep = 10001;
for(int j = 0; j < i;j++){
// 如果从j的位置可以跳过来,而且比minStep次数少,更新
if(j + nums[j] >= i && dp[j] + 1 < minStep){
minStep = dp[j] + 1;
}
}
// 到这里之后dp[i]存储的就是最小的跳跃次数了
dp[i] = minStep;
}
return dp[len - 1];
}
}
C++写法:
#include <vector>
#include <algorithm> // 用于使用min函数
class Solution {
public:
int jump(std::vector<int>& nums) {
int len = nums.size();
// 定义出DP数组
// 对应位置的值表示,跳跃到当前位置所需的最小跳跃次数
std::vector<int> dp(len, 0); // 初始化dp数组,默认所有元素都是0
for(int i = 1; i < len; ++i){
// 这里找到i这个位置所需的最少次数(为什么初始化为10001看题目提示)
int minStep = 10001;
for(int j = 0; j < i; ++j){
// 如果从j的位置可以跳过来,而且比minStep次数少,更新
if(j + nums[j] >= i && dp[j] + 1 < minStep){
minStep = dp[j] + 1;
}
}
// 到这里之后dp[i]存储的就是最小的跳跃次数了
dp[i] = minStep;
}
return dp[len - 1]; // 返回dp数组的最后一个元素,即到达最后一步的最小跳跃次数
}
};
运行时间
时间复杂度和空间复杂度
外层循环遍历数组中的每个元素(除了第一个元素),内层循环则是为了找到到达当前索引位置所需的最少跳跃次数:
- 外层循环从
i = 1
到len - 1
,总共执行len - 1
次。 - 对于每次外层循环,内层循环从
j = 0
遍历到i - 1
。在最坏的情况下,即数组中的每个元素都允许跳到数组的任意位置时,内层循环需要对每个i
都进行i
次比较和可能的更新操作。
那么总的时间复杂度是所有内层循环执行次数的总和,可以表示为:
其中 n
是输入数组 nums
的长度。这意味着,在最坏情况下,算法的时间复杂度是 。
空间复杂度方面,主要是由于使用了一个大小为 n
的 dp
数组来存储到达每个位置所需的最小跳跃次数。因此,空间复杂度是 O(n)。
总结:
- 时间复杂度:
- 空间复杂度:O(n)
解法二:贪心算法
-
初始化:
- 定义三个变量:
currentEnd
:当前跳跃能够达到的最远位置。farthest
:遍历过程中能够达到的最远位置。jumps
:记录跳跃次数。
- 定义三个变量:
-
遍历数组:
- 从数组的起始位置开始遍历直到倒数第二个元素(因为我们只需要知道到达最后一个位置前的最远可达范围)。
- 对于每个位置
i
,更新farthest
为max(farthest, i + nums[i])
,即计算从当前位置出发可以达到的最远位置。 - 如果当前索引
i
等于currentEnd
,意味着我们已经到达了本次跳跃的最远距离,则需要进行一次新的跳跃,并将currentEnd
更新为farthest
,同时增加跳跃计数jumps
。
-
终止条件:
- 当
currentEnd
大于或等于数组的最后一个索引时,表示可以通过之前确定的跳跃策略到达或超过最后一个位置,此时返回jumps
即可。
- 当
Java写法:
class Solution {
public int jump(int[] nums) {
// currentEnd:当前跳跃能够达到的最远位置。
// farthest:遍历过程中能够达到的最远位置。
// jumps:记录跳跃次数。
int jumps = 0;
int currentEnd = 0;
int farthest = 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;
}
}
C++写法:
class Solution {
public:
int jump(vector<int>& nums) {
int jumps = 0, currentEnd = 0, farthest = 0;
for (int i = 0; i < nums.size() - 1; ++i) {
farthest = std::max(farthest, i + nums[i]);
if (i == currentEnd) {
jumps++;
currentEnd = farthest;
if (currentEnd >= nums.size() - 1)
break; // 提前退出循环
}
}
return jumps;
}
};
运行时间
时间复杂度和空间复杂度
时间复杂度
- O(n):仅遍历一次数组。对于数组中的每个元素,我们会检查并更新能够跳跃到的最远距离
farthest
,并在达到当前跳跃边界currentEnd
时进行跳跃计数和边界的更新。由于每个元素只被访问一次,因此时间复杂度是线性的,即 O(n)O(n),其中 nn 是数组nums
的长度。
空间复杂度
- O(1):使用了常数级的额外空间,它只需要跳跃次数 (
jumps
)、当前跳跃能到达的最远位置 (currentEnd
) 和遍历过程中计算出的能到达的最远位置 (farthest
) 等来存储。无论输入数组的大小如何,所使用的额外空间大小保持不变,因此空间复杂度为 O(1)。
总结
- 时间复杂度:O(n)
- 空间复杂度:O(1)
总结
EZEZ,但是贪心的思路真的是不如DP那样的清楚明确,所以还是各有所爱吧算是。