跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法一:深搜
思路:从后往前推,首先想到的是如果没有任何一个节点可以一次跳到终点位置那么一定不能到达终点,返回结果就是false。反之如果有这样的节点,那么再依次对这些节点找是否存在节点能一次跳到该节点,以此类推,这就是深搜。
class Solution {
private:
bool dfs(int index,vector<int>& nums){
if(index==0) return true;//出口,最终找到了索引为0的节点能够完成从头到尾的完整的跳跃
for(int i=0;i<index;i++){
//判断当前遍历的节点能否跳到我们这次调用函数时给的节点,如果能就再从这个节点往里搜
if(nums[i]+i>=index) return dfs(i,nums);
}
return false;//之前那么多出口都没出去,说明不能完成从头到尾的跳跃
}
public:
bool canJump(vector<int>& nums) {
if(nums.size()==1) return true;
return dfs(nums.size()-1,nums);
}
};
方法二:递归太慢啦,换一种思想,给它改为非递归——用贪心算法,一步步从局部最优推到全局最优。我们每次都找到最远可以到达的位置max_index
,并更新这个位置,最终如果这个位置大于nums.size()-1
则可以到达终点。
class Solution max_index{
public:
bool canJump(vector<int>& nums) {
if(nums.size()==1) return true;
int size = nums.size();
int max_index = 0;
for(int i=0;i<size;i++){
if(max_index>=i)max_index =max(max_index,i+nums[i]);
if(max_index>=size-1) return true;
}
return false;
}
};
跳跃游戏Ⅱ
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法一:继续深搜——反向查找(可能会超时)
之前在用深搜时,是从前往后遍历来找到能完成一次跳跃的节点,所以第一次能找到的节点是当前离终点最远的位置,并且如果它继续深搜下去能找到一条从起点跳到终点的路径,那么这个节点就是我们要找的最短跳数路径上的必经节点之一,直接改变一下深搜的求解方式即可。
请注意这里题目已经说明必然可以从起点出发到达最后一个位置了。
class Solution {
private:
int dfs(int index,vector<int>& nums,int mintimes){
if(index==0) return mintimes;
for(int i=0;i<index;i++){
if(nums[i]+i>=index) return dfs(i,nums,mintimes+1);//每找到一个节点就让跳数加一
}
return 0;
}
public:
int jump(vector<int>& nums) {
int mintimes = 0;
if(nums.size()==1) return 0;
return dfs(nums.size()-1,nums,mintimes);
}
};
方法二:继续用贪心算法——反向查找(可能会超时)
我们的目标是到达数组的最后一个位置,我们可以考虑最后一步跳跃前所在的位置,该位置通过一次跳跃能够到达最后一个位置。
那么如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上来看,我们可以「贪心」地选择距离最后一个位置最远的那个位置,也就是对应下标最小的那个位置。因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。
找到最后一步跳跃前所在的位置之后,我们继续贪心地寻找倒数第二步跳跃前所在的位置,以此类推,直到找到数组的开始位置。
class Solution {
public:
int jump(vector<int>& nums) {
int index = nums.size() - 1;
int mintimes = 0;
//从后往前找路径,反向查找,直到找到出发位置
while (index > 0) {
for (int i = 0; i < index; i++) {
if (i + nums[i] >= index) {
index = i;//更新一下最远节点
mintimes++;
break;
}
}
}
return mintimes;
}
};
时间复杂度O(n2)
方法三:优化一下时间,采用正向查找,如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数,就可以把时间复杂度从O(n2)
降到O(n)
。
直观上看来,就是每一次的跳跃都为下一次的跳跃更新了起点和终点的范围,记录跳越次数,如果下一次跳跃的范围包括了终点就得到了结果。
class Solution{
public:
int jump(vector<int>& nums)
{
int steps = 0;
int end = 0;
int maxPos = 0;
for (int i = 0; i < nums.size() - 1; i++)
{
maxPos = max(nums[i] + i, maxPos);
if (i == end)
{
end = maxPos;
steps++;
}
}
return steps;
}
};