题目链接 跳跃游戏
这个题让我明白了,算法题直接暴力是不行的,不要有侥幸心里。但是,当你只会暴力的时候,还是先运行出来再进行修改吧。不然好的方法是不太容易直接想出来的。
- 方法一:暴力(这个暴力也不是很容易的,递归加回溯)
思路:
1 明确当前位置能否到达最后一个位置,最好的判定时 当前坐标i,对应的值nums[i] + i是改点能走到的最大坐标处,所以当nums[i] + i >= nums.size()时,可以直接返回了。另一个想法就是nums[i] - (nums.size() -i) >=0,其实都是一样的,这里我用的是后面的判定方法,也是递归的出口。
2 明确当前点i,需要就行多少次递归,即nums[i]次,因为他可以从接下来i+1到i+nums[i]内任何一个位置出发,去寻找能否到达最终位置,需要一个一个尝试,这也是这种方法慢的地方。
//时间超时
class Solution {
public:
void solve(vector<int> nums, bool &sig, int l, int n){
if(l==n || nums[l] - (n - l) >=0){ //出口,sig是一个标志的返回值
sig= 1;
return;
}
int i =1;
// cout<<nums[l]<<" ";
for(i =1; i <= nums[l]; i++){
// if(nums[l + i] == 0 ){continue;}
solve(nums,sig, i+ l, n); //依次递归从l 到 l+nums[l]中间的任何一个位置。
}
//这里有一个改进的地方,就是从l处开始,先搜索l + nums[l],但是复杂度是一样的。还是过不了
}
bool canJump(vector<int>& nums) {
int n = nums.size();
bool sig=0;
solve(nums,sig, 0, n-1);
return sig;
}
};
- 方法二:递归中记忆法,说白了就是剪枝
思考:[9,5,3,2,1,0,2,0]的执行过程,用上面的方法是5->4>3>2>1>0>0>1>.....
后面就不看了,注意到0时,走不通,回到1,1对应的只有一个递归,走完了回到2,此时2对应的第一个递归(i = 1)走完了,
下一个时(i = 2),对应0,但是我们知道0已经走过,但是从0开始是无法走通的,但是程序仍然会再次进行递归,所以造成不必要的浪费。
思考:定义一个数组,代表改点能否到达最后的位置,如果能则为1,就直接返回,说明改点走过了,不用走了,走也不通。要不然也不会回溯啊。
class Solution {
public:
int src[1000] = {0};
void solve(vector<int> nums, bool &sig, int l, int n){
if(src[l] == 1){ return ;}
src[l] = 1;
if(l==n || nums[l] - (n - l) >=0){
sig= 1;
return;
}
//icout<<nums[l]<<" ";
for(int i =nums[l]; i >=1; i--){
if(nums[l + i] == 0 ){continue;}
solve(nums,sig, i+ l, n);
}
}
bool canJump(vector<int>& nums) {
int n = nums.size();
// int src = new src[n];
bool sig=0;
solve(nums,sig, 0, n-1);
return sig;
}
};
//这个提示爆内存溢出,其实就是栈溢出,很正常,数据多了用递归就是这样。
- 方法三:动态规划,从后向前
思路:倒着判断点i能否到达最后点,如果能设置为1,再判断前面的点j,能否到i。以此推到初始点。
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int src[n] = {0};
src[n-1] = 1;
for(int i = n -2; i >= 0; i--){
int count = min(i + nums[i], n - 1); //i一定能到达j,如果j能到达最后,则i也能
for(int j = i + 1; j <= count; j++){
if(src[j] == 1){
src[i] =1;
break;
}
}
}
return src[0] == 1;
}
};
//感谢老天,终于过了,但是时间复杂度为O(n2),太高了,继续
- 方法四:贪心
思考:方法三中第二层循环的意思何在?说白了就是记录一个点,改点能否到达下一个可行点。但是记录的有点多,他记录了每一个点,我们只需要记录最左边的点能否到达终点,然后在最左边点1再找一个相对最左边的点2能否到达1,即可。
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int leftPos = n - 1;
for(int i = n - 1; i >=0; i--){
if(nums[i] + i >= leftPos){
leftPos = i;
}
}
return leftPos == 0;
}
};
//O(n)
/*
相对于第三种
其实count 就是nums[i] + i, j就相当于leftPos
*/