LeetCode--55. Jump Game
題目連結:https://leetcode.com/problems/jump-game/
這是個十分有趣的題目。從起點(陣列索引0)出發,移動k(k<=nums[0])個位置到達下一個位置(陣列索引為i),繼續移動k(k<=nums[i])個位置,就這樣看是否有一條走法能到達終點(陣列索引nums.length-1)。當走到某個位置i時nums[i]==0時就意味著這個走法行不通了!可以看到理論上每一個位置i上都有nums[i]個選擇的步數。
我的思路:用回溯法來暴力搜尋,在當前到達的位置狀態i上,有1:nums[i]種選擇(只要nums[i]+i<nums.length),然後就進入下一個(位置)狀態繼續搜尋。
class Solution { public static boolean canJump(int[] nums) { return recursion(0,nums); } public static boolean recursion(int index,int[] nums) { if(index==nums.length-1) return true; if(nums[index]==0) return false; boolean ret=false; for(int i=1;i<=nums[index];i++) if(index+i<nums.length) ret=ret || recursion(index+i,nums); return ret; } }
時間複雜度:O(2^n),從起點到達終點有2^n的走法(理論上限)
空間複雜度:O(n),遞迴時的棧記憶體開銷
暴力是真暴力,就是TLE了!!!
其實審查上面的過程,到達位置狀態i的策略選擇可以由很多很多種,而後面從i為起點的搜尋只需要進行一次,然而我的演算法存在大量重複。
看了Solutions後豁然開朗,https://leetcode.com/problems/jump-game/solution/這篇題解將回溯、動態規劃講的很全面很詳細了,下面借鑑這篇題解寫寫自己的理解。
上面的步驟在實踐中可以稍微優化一下,從較大調步開始搜尋更快到達終點,但演算法理論複雜度沒變。
for(int i=nums[index];i>0;i--)
自頂向下的動態規劃:
用一個memo陣列儲存位置i對於終點的可達性(accesibility),終點對於其本身是可達的,首先將memo陣列memo[0:nums.length-2]=Unknown;
enum Mark{
Accessible,Inaccessible,Unknown
}
public class Solution {
public Mark[] memo;
public boolean canJump(int[] nums) {
memo=new Mark[nums.length];
for(int i=0;i<memo.length;i++)
memo[i]=Mark.Unknown;
memo[nums.length-1]=Mark.Accessible;
return canJumpFromIndex(0,nums);
}
public boolean canJumpFromIndex(int index,int[] nums){
if(memo[index]!=Mark.Unknown)
return memo[index]==Mark.Accessible? true:false;
int min_index=Math.min(nums[index]+index,nums.length-1);
for(int i=min_index;i>=index+1;i--)
{
if(canJumpFromIndex(i,nums))
{
memo[index]=Mark.Accessible;
return true;
}
}
memo[index]=Mark.Inaccessible;
return false;
}
}
時間複雜度為:O(n^2),因為對於每個狀態位置i,都有可能將[i+1:nums.length-1]遍歷一遍
空間複雜度:O(n),遞迴的棧空間開銷O(n),額外陣列開銷O(n)
自底向上的動態規劃:自底向上一般能夠將遞迴改成迴圈的形式,而且能夠減少中間重複的計算
確定第i個位置的可達性只需要檢查第[i+1,i+min(nums[i]+i,nums.length-1)]的位置上的可達性,真是tricky!!!
public class Solution {
public Mark[] memo;
public boolean canJump(int[] nums) {
memo=new Mark[nums.length];
for(int i=0;i<memo.length;i++)
memo[i]=Mark.Unknown;
memo[nums.length-1]=Mark.Accessible;
for(int i=nums.length-2;i>=0;i--)
{
int max_index=Math.min(nums[i]+i,nums.length-1);
for(int j=max_index;j>i;j--)
{
if(memo[j]==Mark.Accessible)
{
memo[i]=Mark.Accessible;
break;
}
}
}
return memo[0]==Mark.Accessible?true:false;
}
}
enum Mark{
Accessible,Inaccessible,Unknown
}