跳跃游戏 Jump Game 分析与整理

参考文章 点这儿查看
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以向后跳跃的最大长度。

问题一

判断你能不能到达数组的最后一个位置。
思路:从数组的第一个位置开始,往后一格一格遍历数组,当所遍历的位置还没超出可reach范围时,根据跳力更新可reach范围,可遍历的范围必须小于等于reach值。若可reach范围可覆盖数组最后一个位置,则可到达;若不可覆盖则不可到达。

class Solution1{
    public boolean jumpGame(int[] num){
        int N=num.length,reach=0;
        for(int i=0;i<=reach;i++){
            if(reach<i+num[i]) reach=i+num[i];
            if(reach>=N-1) return true;
        }
        return false;
    }
}

问题二

若可以到达数组最后一个位置,求出所用最小步数;若不可到达,返回-1。
思路:参照问题一,只不过在遍历时要更加细化一些,在遍历完x步可到的位置后,和遍历x+1步可到的位置前时,更新步数参数step为x+1。

class Solution2{
      public int jumpGame(int[] num){
        int N=num.length,i = 0,reach=0,step=0,nextReach=0;
        if(N=1) return 0;
        while(i<=reach) {
            for ( ; i <= reach; i++) {
                if (nextReach < i + num[i]) nextReach = i + num[i];
                if (nextReach >= N - 1) return step+1;
            }
            reach=nextReach;
            ++step;//更新step数值后,第step步最远能走到nextReach处
            nextReach=0;
        }
        return -1;
     }
}

问题三

对于数组中的每个位置,到达此位置所需的最小步数是多少。如不可达则为-1。
思路1:参照问题二的解答,每步可达的范围其实已经求出来了。

class Solution3{
      public int[] jumpGame1(int[] num){
        int N=num.length,i=0,j=1,reach=0,step=0,nextReach=0;
        int[] stepNum=new int[N];
        stepNum[0]=0;
        while(i<=reach) {
            for ( ; i <= reach; i++) {
                if (nextReach < i + num[i]) nextReach = i + num[i];
                if (nextReach >= N - 1) {
                    for(;j<=N-1;j++){ stepNum[j] =++step; }
                    return stepNum;
                }
            }
            reach=nextReach;
            ++step;
            nextReach=0;
            for(;j<=reach;j++){ stepNum[j] =step; }
        }
        for(;j<=N-1;j++){ stepNum[j] =-1; }
        return stepNum;
      }
}

思路2:一种很糟糕的暴力解答,遍历每个位置,对于每个位置遍历其跳力,让位置步数+1去尝试“松弛”位置跳力后对应的位置的最小步数。

class Solution3{
      public int[] jumpGame2(int[] num){
        int N=num.length,reach=0,nextReach=0;
        int[] stepNum=new int[N];
        for(int i=1;i<N;i++){ stepNum[i]=Integer.MAX_VALUE; }
        stepNum[0]=0;
        for(int i=0;i<=reach;i++){
            for(int j=0;j<=num[i];j++){
                if(i+j>N-1) return stepNum;
                if(stepNum[i]+1<stepNum[i+j]) stepNum[i+j]=stepNum[i]+1;
                if(nextReach<i+j) nextReach=i+j;
            }
            if(reach<nextReach) reach=nextReach;
            nextReach=0;
        }
        for(int i=reach+1;i<N;i++) stepNum[i]=-1;
        return stepNum;
     }
}

很明显,这种糟糕的解法时间复杂度是O(n^2),因为它有很多次无效的尝试松弛的操作,若要在此基础上优化,考虑两点:

  • i 不能按++来遍历,要挑着来遍历,那 i 怎么选,在当前 i 的可跳跃范围里面选一个位置 i+j,这个位置再往后面能跳的位置最远,那么 i 的下一个值就选 i+j
  • 对于选定的 i(假设属于跳x次能到的范围),在每次加跳力j时,使得 i+j 从跳x+1次的范围起点处开始遍历(也就是说 j 不一定从1开始),那就是线性时间复杂度了。(但这样做,上一点在挑选 i 时就不全面了,因为上一点挑选 i 时,j 是要从1到num[i]范围内都要比较一下的,因此这两点矛盾了;还有,第二点这样想的目的明显是想要线性时间复杂度,那就相当于在兜圈子了,直接用3.1的解答吧)

因此,按第一点来优化,第二点当我没说(=_=),注意优化后它也不是线性时间复杂度。

class Solution3{
      public int[] jumpGame21(int[] num){
        int N=num.length,reach=0,nextNextReach=0,nextI=0;
        int[] stepNum=new int[N];
        for(int i=1;i<N;i++){ stepNum[i]=Integer.MAX_VALUE; }
        stepNum[0]=0;
        for(int i=0;i<=reach;){
            for(int j=0;j<=num[i];j++){

                if(i+j>N-1) return stepNum;
                if(stepNum[i]+1<stepNum[i+j]) stepNum[i+j]=stepNum[i]+1;

                if(nextNextReach<i+j+num[i+j]) {
                    nextNextReach=i+j+num[i+j];
                    nextI=i+j;}
            }

            if(reach<nextNextReach){ 
                reach=nextNextReach;}
            else{ break;}

            nextNextReach=0;
            i=nextI;
        }
        for(int i=reach+1;i<N;i++) stepNum[i]=-1;
        return stepNum;
     }
}

猜你喜欢

转载自blog.csdn.net/shawn_chan/article/details/80999573