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