leetcode 403青蛙过河
403. 青蛙過河
- 題目
- 分析
- 超時代碼
- 代碼1
- 代碼2
- 代碼3
- 通過代碼
- 代碼4
- 代碼5
- 代碼6
題目
一只青蛙想要過河。 假定河流被等分為 x 個單元格,并且在每一個單元格內都有可能放有一石子(也有可能沒有)。 青蛙可以跳上石頭,但是不可以跳入水中。
給定石子的位置列表(用單元格序號升序表示), 請判定青蛙能否成功過河(即能否在最后一步跳至最后一個石子上)。 開始時, 青蛙默認已站在第一個石子上,并可以假定它第一步只能跳躍一個單位(即只能從單元格1跳至單元格2)。
如果青蛙上一步跳躍了 k 個單位,那么它接下來的跳躍距離只能選擇為 k - 1、k 或 k + 1個單位。 另請注意,青蛙只能向前方(終點的方向)跳躍。
請注意:
石子的數量 ≥ 2 且 < 1100; 每一個石子的位置序號都是一個非負整數,且其 < 231; 第一個石子的位置永遠是0。示例 1:
[0,1,3,5,6,8,12,17]
總共有8個石子。
第一個石子處于序號為0的單元格的位置, 第二個石子處于序號為1的單元格的位置,
第三個石子在序號為3的單元格的位置, 以此定義整個數組…
最后一個石子處于序號為17的單元格的位置。
返回 true。即青蛙可以成功過河,按照如下方案跳躍:
跳1個單位到第2塊石子, 然后跳2個單位到第3塊石子, 接著
跳2個單位到第4塊石子, 然后跳3個單位到第6塊石子,
跳4個單位到第7塊石子, 最后,跳5個單位到第8個石子(即最后一塊石子)。
示例 2:
[0,1,2,3,4,8,9,11]
返回 false。青蛙沒有辦法過河。
這是因為第5和第6個石子之間的間距太大,沒有可選的方案供青蛙跳躍過去。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/frog-jump
分析
動規的題目,這個題的狀態應該就是所處的位置和能跳的距離.按照這個來定.
查找下一個石頭有三種找法
bool check(vector& stones,int now,int jump,vector<vector>& memo)
check的返回值就是對于從now索引的石頭開始跳,現在跳的距離是jump的能不能到最后一塊石頭,memo的值也和check返回值一一對應
jumpsize的大小不會超過1001,因為一共有1000塊石頭,每一步就算只跳到下一塊,那么最多跳1000下,一下最多jumpsize增加1,所以jumpsize最多為1001.就可以在一個stones.size()大小的存儲了
這題我做吐了,寫了兩種都超時,又模仿著官方題解寫了兩種,又超時.
回溯的基本的思路:
bool check(vector<int>& stones,int now,int jump,vector<vector<bool>>& memo){if(memo[now][jump])return memo[now][jump];int tmp=search(now+1,sz-1,stones[now]+jump,stones);if(tmp!=-1&&check(stones,tmp,jump,memo)){memo[now][jump]=true;return true;}tmp=search(now+1,sz-1,stones[now]+jump-1,stones);if(tmp!=-1&&check(stones,tmp,jump-1,memo)){memo[now][jump-1]=true;return true;}tmp=search(now+1,sz-1,stones[now]+jump+1,stones);if(tmp!=-1&&check(stones,tmp,jump+1,memo)){memo[now][jump+1]=true;return true;}memo[now][jump]=now==sz-1;return memo[now][jump];首先看備忘錄里有沒有這個值,之后嘗試三種步子大小.如果有一種成功了,就return true走掉了.三種都沒有成功的就說明它沒有下一步了,檢查它是否是最后一塊石頭(這個也可以在開頭檢查)
超時代碼
代碼1
class Solution { public:bool res=false;int sz;int find(int left,int target,vector<int>&stones){int right=sz-1;while(left<=right){int mid=left+(right-left)/2;if(stones[mid]==target) return mid;else if(stones[mid]>target) right=mid-1;else left=mid+1;}return -1;}void backtrace(int now,int step,vector<int>& stones){//找到下一個石頭if(res) return;int tmp=find(now+1,stones[now]+step,stones);if(tmp==-1) return;if(tmp==sz-1) {res=true; return;}backtrace(tmp,step+1,stones);backtrace(tmp,step,stones);if(step-1>0)backtrace(tmp,step-1,stones);}bool canCross(vector<int>& stones) {sz=stones.size();;backtrace(0,1,stones);return res;} };這個寫的就是單純的回溯(過了16個),這個超時我認的
由于dp就是帶備忘的回溯,所以我就加了個數組記錄
代碼2
class Solution { public:int sz;bool check(vector<int>& stones,int now,int jump,vector<vector<bool>>& memo){if(memo[now][jump])return memo[now][jump];for(int i=now+1;i<sz;i++){int dis=stones[i]-stones[now];if(dis>=jump-1&&dis<=jump+1){if(check(stones,i,dis,memo)){memo[now][dis]=true;return true;}}}memo[now][jump]=now==sz-1;return memo[now][jump];}bool canCross(vector<int>& stones) {sz=stones.size();vector<vector<bool>> memo(sz,vector<bool>(sz,false));return check(stones,0,0,memo); } };這個跟官方的題解寫的差不多啊.為啥還是超時只過16個
嗯,我不在數組里遍歷了,再優化下,改成二分查找,這樣就是找三個指定的值了
代碼3
class Solution { public:int sz;int search(int left,int right,int tar,vector<int> &stones){while(left<=right){int mid=left+(right-left)/2;if(stones[mid]==tar) return mid;else if (stones[mid]<tar) left=mid+1;else right=mid-1;}return -1;}bool check(vector<int>& stones,int now,int jump,vector<vector<bool>>& memo){if(memo[now][jump])return memo[now][jump];int tmp=search(now+1,sz-1,stones[now]+jump,stones);if(tmp!=-1&&check(stones,tmp,jump,memo)){memo[now][jump]=true;return true;}tmp=search(now+1,sz-1,stones[now]+jump-1,stones);if(tmp!=-1&&check(stones,tmp,jump-1,memo)){memo[now][jump-1]=true;return true;}tmp=search(now+1,sz-1,stones[now]+jump+1,stones);if(tmp!=-1&&check(stones,tmp,jump+1,memo)){memo[now][jump+1]=true;return true;}memo[now][jump]=now==sz-1;return memo[now][jump];}bool canCross(vector<int>& stones) {sz=stones.size();vector<vector<bool>> memo(sz+1,vector<bool>(sz+1,false));return check(stones,0,0,memo); } };做到這我快哭了,咋還是超時只過16個.這我改的跟沒優化的一樣…
通過代碼
下面每一個通過對代碼都有一些額外的小技巧,我是想不到,也是參考別人的
代碼4
class Solution { public:int sz;unordered_map<int,bool> memo;bool check(vector<int>& stones,int now,int jump){int key = now | jump << 11;if(now>=sz-1) return true;if(memo.count(key)) return memo[key];for(int i=now+1;i<sz;i++){int dis=stones[i]-stones[now];if(dis>=jump-1&&dis<=jump+1){if(check(stones,i,dis)){return memo[key]=true;}}else if(dis>jump+1) break;}return memo[key]=false;}bool canCross(vector<int>& stones) {sz=stones.size();return check(stones,0,0); } };這個代碼是參考大佬的,亮點在于使用了狀態的壓縮,即將now和jump壓縮成一個數,然后存在unordered_map里,在備忘錄里提出的速度就比較快,還節省空間(使用map也能過,就是慢點)
菜雞的疑問,這vector<vector> 就這么慢嗎,
map.count(key) ->返回 map中key的個數,因為map中key唯一,所以有就返回1,沒有就返回0
map.find(key) ->返回值為key的迭代器,有就返回正常迭代器,沒有就返回map.end()
代碼5
回溯,剪枝
//12ms,9MB class Solution { public:bool res=false;int sz;int find(int left,int target,vector<int>&stones){int right=sz-1;while(left<=right){int mid=left+(right-left)/2;if(stones[mid]==target) return mid;else if(stones[mid]>target) right=mid-1;else left=mid+1;}return -1;}void backtrace(int now,int step,vector<int>& stones){//找到下一個石頭if(res) return;int tmp=find(now+1,stones[now]+step,stones);if(tmp==-1) return;if(tmp==sz-1) {res=true; return;}backtrace(tmp,step+1,stones);backtrace(tmp,step,stones);if(step-1>0)backtrace(tmp,step-1,stones);}bool canCross(vector<int>& stones) {sz=stones.size();for (int i = 1; i < stones.size(); ++i) {if (i > 3 && stones[i - 1] * 2 < stones[i]){return false;}}backtrace(0,1,stones);return res;} };這個代碼就是"代碼1"加了剪枝,結果雙百.
剪枝
這個剪枝就是根據第i塊石頭的時候最多跳i個單位(這是i及以前的石頭能跳的最遠距離),所以所以如果下一塊石頭在2*stones[i]外面,那就跳不到了,只能是false;我是真沒想到!
代碼6
//412ms,37MB class Solution { public:int sz;unordered_map<int,unordered_set<int>> map;bool canCross(vector<int>& stones) {sz=stones.size();for(int i=0;i<sz;i++){map[stones[i]]=unordered_set<int>();}map[0].insert(0);for(int i=0;i<sz;i++){for(const int &step:map[stones[i]]){for(int j=step-1;j<=step+1;++j){if(j>0&&map.count(stones[i]+j)){map[stones[i]+j].insert(j);}}}}return map[stones[sz-1]].size()>0;} };這個算法是官方的最后一種動態規劃的解法,具體就是對每一個stones[i]存入map,每個stones[i]對應一個set,里面記錄到達stones[i]的上一個步長,在stones[i]進行狀態轉移的時候,就可以使用set里面的步長每一個的+1&&-1的步長進行下一塊石頭的嘗試,如果有,就將現在的步長記入下一塊石頭的set里.
初始就是在第0(索引)塊的時候步長為0,保證第一步就是只有step=1.
最終結果就是看最后一塊石頭的set里是否有東西,因為set就是記錄到達的上一步長度,有就表示可以到.
tips:
map里套set的構造
map里套set的set遍歷
for(const int &step:map[stones[i]])const不能少,否則報錯
感覺還是最后一種方法是正道,回溯剪枝雖然快,但感覺是數據水,出的都是0,1,2,999這種,正常的剪肯定剪不了多少,剪不了還是要回溯,回溯就太慢了.
總結
以上是生活随笔為你收集整理的leetcode 403青蛙过河的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下飞鸽传书项目设计书,linu
- 下一篇: android rar工具,RAR压缩工