1. 程式人生 > >跳躍遊戲 Jump Game 分析與整理

跳躍遊戲 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

對比思想二的那種方法,在一次for迴圈裡面,就是在i+j這個地方將nextReach更新到最大值,到了i+num[i]的地方reach就變成nextReach了,然鵝現在這種方法卻是將i變為i+j再往前遍歷,光看i它是在跳著走,但遍歷它的跳力其實可以看作思想二中的i向前走。於是,思想二中的i是一直向前走,而本方法是走到跳x次能到的最遠範圍後,再退幾步到i+j再往前走。因此考慮第二個改進條件

  • 對於選定的 i(假設屬於跳x次能到的範圍),在每次加跳力j時,使得 i+j 從跳x+1次的範圍起點處開始遍歷(也就是說 j 不一定從1開始),那就是線性時間複雜度了。(這樣做其實跟前面的解答是一種思想)

    這裡寫圖片描述

如圖,紅色是更新i後的

class Solution3{
      public int[] jumpGame21(int[] num){
        int N=num.length,reach=0,reachAgo=0,nextReach=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=reachAgo+1-i;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+num[i+j]) {
                    reachAgo=reach;
                    nextReach=i+j+num[i+j];
                    nextI=i+j;}
            }

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

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