Leetcode55:跳跃游戏
题目:
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
思路:
利用贪心法则,从后往前找最先能够跳到终点的位置。贪心没错,因为这个问题是个累计问题,一旦能跳到这么前面到终点的,为什么要跳后面再到终点,你再回退下前面的路反而可选择的少了,前面的路要能够跳到这么远,就是越前面,压力越少,因为是一个累计最远能跳多远的问题。
代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
vector<int> v;
for(int i = 0;i < nums.size();i++){
v.push_back(i+nums[i]);
}
if(v.size() == 1)
return true;
int tmp = nums.size()-1;
int step = 0;
bool bark = false;
for(int i = 0;i < tmp;i++){ //注意到是i<tmp
if(v[i] >= tmp){ //找到的是从左到右最先能够跳到终点的!
step++;
if(i != 0){
tmp = i; //如果不是起点 这个时候新的终点变成i了
i = -1;
}
else{
bark = true;
break;
}
}
}
return bark;
}
};
上面的思路比较复杂,稍微修改下甚至可以求步数了,我们换种简单的思路! 从左往右对每个位置都有能到达的最远距离,循环过来,如果最远距离超过或者等于数组右边界就能走到,但是还要增加条件,某个位置能够有机会发挥他的步数取决于他的确是在当前能够走的最远边界内,超过最远边界就走不了了。
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int rightmost = 0;
for (int i = 0; i < n; ++i) {
if (i <= rightmost) { //注意到我们这边能够让你走的前提是 右边界必须大于等于我的索引,所以如果出现某个索引这边是0了,他的右边界不更新,后面的i无法参与迭代啊循环完毕就结束了。
rightmost = max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
}
return false;
}
};
1306. 跳跃游戏 III
题目:
这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。注意,不管是什么情况下,你都无法跳到数组之外。
思路1:
这种题目我第一个想到的思路就是dfs深搜,终止条件是越界或者这个路走过了,就是记忆化搜索,之前走过的都做了选择你还走干啥。或者就是走到任一,一个目标点值等于0了然后return。但是和想的一样超时了,不过还好,是54/55,至少说明我的思路没问题~,阿这后来发现vis数组可以引用啊,引用之后发现不超时了呵呵。下面附上代码
思考能否进行优化呢?因为我就算return了也是要走完所有的路,没有剪枝导致超时!所以第二种方法进行剪纸,就是对某一路就直接设置return,不用或了!是否走过也在循环中直接判断!而不是进去后判断这样就干练很多。
class Solution {
public:
bool flag=false;
bool canReach(vector<int>& arr, int start) {
//现在是可以左右横跳了 而且是固定步长而不是最大步长
int n=arr.size();
if(n==1){
return true;
}
//试试看dfs
vector<int>vis(arr.size());
return dfs(arr, start,vis);
}
//要增加一个visst数组啊记忆化 等会加上去
bool dfs(vector<int>& arr, int start,vector<int> & vis){
if(start<0 || start>=arr.size() || vis[start]==1){
return false;
}
vis[start]=1;
if(arr[start]==0){
vis[start]=0;
return true;
}
return dfs(arr,start+arr[start],vis) || dfs(arr,start-arr[start],vis);
}
};
剪枝优化后的代码,每次对某一侧进行处理,出现问题就不走或者return true!但是注意到,记忆化的操作是针对当前层能走说明没走过然后进去的时候给他1或者true,每次都要产生一个副本,我们能否用引用的方式呢?在这里是可以的!在这里我就不加上去了,加上去之后内存就打败很多了。引用不引用是考虑到在某一层的操作是否会影响vis数组。第一种方法为什么必须要不能引用呢
class Solution {
public:
bool canReach(vector<int>& arr, int start) {
//现在是可以左右横跳了 而且是固定步长而不是最大步长
//试试看dfs
vector<bool>vis(arr.size());
return dfs(arr, start,vis);
}
//要增加一个vis数组啊记忆化
bool dfs(vector<int>& arr, int start,vector<bool> vis){
if(arr[start]==0){
return true;
}
vis[start]=true;
//对左侧分支处理
int left=start-arr[start];
if(left>=0 &&!vis[left] && dfs(arr,left,vis)){
return true;
}
//对右侧分支处理
int right=start+arr[start];
if(right<arr.size() && !vis[right]&& dfs(arr,right,vis)){
return true;
}
return false;
}
};