55.跳跃游戏
class Solution {
public:
bool canJump1(vector<int>& nums) {
if(nums.size()<=1) return true;
int len=nums.size();
int *flag=new int[len];
//flag={0};//使用变量定义长度时,定义和初始化需要分开
for(int i=0;i<len;i++) flag[i]=0;
//判断每一个元素是否可以抵达
queue<int> judge;
int it=0;
judge.push(it);
while(!judge.empty()){
it=judge.front();
printf("%d\n",it);
judge.pop();
//判断队首元素可以到达的位置并入栈
for(int len=nums[it],i=1;i<=len;i++){
if(it+i==nums.size()-1) return true;//最后一个可达
if(flag[it+i]==0){
judge.push(it+i);//这是由it出发可以访问到的所有位置
flag[it+i]=1;
}
//flag[it+i]==1说明之前已经执行过一次从这个位置出发的所有判断了
}
}
return false;
}
//贪心-不断维护一个可以到达的最大下标
bool canJump(vector<int>& nums){
int maxIndex=0;
for(int it=0;it<nums.size();it++){
if(maxIndex>=nums.size()-1) return true;
//if(it>=maxIndex) return false;//已经不可达了
if(it<=maxIndex) maxIndex=max(it+nums[it],maxIndex);
}
if(maxIndex>=nums.size()-1) return true;
else return false;
}
};
从Nums[0]开始入队,只要队不空的情况下出队一个元素,元素的值为其在nums中的下标,在队中的下标表示这个位置是可以到达的,那么考虑这个位置可以抵达的所有位置下标都入队,如果已经入过了(查flag)就不再重复否则超时,如果队空了,flag[nums.size()-1]仍然为0,那说明不可达,一旦flag[nums.size()-1]为1,return true
45.跳跃游戏2
关键是识别【同一跳】,没思路看了一下题解,结合这位哥的解释,我给出的【同一跳】可以这么理解,如果从i开始跳,从i+1的位置到i+nums[i]的位置都是同一跳,不跳就不存在这种考量。
先解释一下官方给出的贪心题解,参考了以下这位的注释。
每次找到可到达的最远位置,顺着遍历一次。如果当前位置可达(i<最远可达的距离),那么当前位置就要考虑是否跳,如果跳的话,以当前位置作为起点,最远可以跳到i+nums[i],如果最远可达的距离>i+nums[i],那在这里跳就是多跳,反之,在这里跳,同时,因为跳出了最远可达距离,所以【同一跳】的范围需要更新,【同一跳】是从决定从i开始跳的下一条i+1的位置一直到新的最远可达距离。
我在搞清楚【同一跳】的定义后,想到了使用树形结构来描述这种跳跃,把可达的下标作为存在树中的值/元素,那么处于同一层的下标都是使用相同跳数可以到的位置,从他们出发再深一层,就是再跳一步,如果在生成新一层元素的过程中到达/超出nums的最后位置,立刻终止,此时的层数就是跳数,和55题相似,会有一些位置不断被可达,但是不应该不断被入队,如果在这次可达之前已经入队过了,那说明上一次可达使用了更少的步数,这一次就不是一个合适的跳跃方式。
延续55题的想法,跳跃的结构类似于层次访问一个树形结构,使用两个队列进行层次遍历,第一层是0,将0这个下标入队列1,当两个队不是都空的时候,说明可以继续跳跃,选取空的队列,弹出头元素(可访问到的下标),读取nums[i],对从i可达的所有下标入另一个队列,可能会出现已经考虑过的情况,所以参考55题使用flag来记录是否已经考虑过当前下标作为出发点的情况,如果考虑过直接跳过,如果可达的下标超过了最远下标,直接返回当前step。每次进行队列切换的时候(一个空了,另一个有下标元素),说明已经到了新的层次了,也就是新的一步(思路:从i开始往前跳跃[1,nums[i]]个位置,都是同一步,在同一层)
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size()<=1) return 0;
//最小跳跃次数,要求每次尽可能跳的远
//如果当前下标可达,可以从这里跳,或者不跳
queue<int> judge0,judge1;
int step=0;//第0个一定走
//维护一个判断是否考虑过当前index的flag数组,如果之前已经考虑过,再碰到该下标时就不考虑,因为后考虑的步数一定大于先考虑的
int *flag=new int[nums.size()];
for(int i=0;i<nums.size();i++){
flag[i]=0;
}
flag[0]=1;
judge0.push(0);//把能抵达的入队
while(!judge0.empty()||!judge1.empty()){
if(step%2==1){
step++;
while(!judge1.empty()){
int index=judge1.front();
for(int j=1;j<=nums[index];j++){
if(index+j>=nums.size()-1) return step;
if(flag[index+j]==0){
//还没考虑过从index+j出发
judge0.push(index+j);
flag[index+j]=1;
}
}
judge1.pop();
}
}
else if(step%2==0){
step++;
//judge0 out judge1 in
while(!judge0.empty()){
int index=judge0.front();
for(int j=1;j<=nums[index];j++){
if(index+j>=nums.size()-1) return step;
if(flag[index+j]==0){
//还没考虑过从index+j出发
judge1.push(index+j);
flag[index+j]=1;
}
}
judge0.pop();
}
}
}
return step;
}
};
总结
我自己写的时候,一开始没有涉及flag数组,结果超时了,所以flag还比较重要,是我觉得我在这两题中一以贯之的思想;
【同一跳】是如何定义的;
//写给自己,先考虑中间的大多数,再考虑头尾情况。