前幾天還在踟躕我應該注重培養作項目的能力仍是修煉算法以及數據結構,而後發現這個場景有點似曾相識。曾幾什麼時候的一個月裏,我有三件比較重要的事情要解決,在那個月剛開始的時候我一直在想我應該從那件事情開始着手,甚至在腦海裏給這三件事情的重要性排個序,思緒爭執不下,煩躁之極,不如玩玩遊戲散散心吧,或許等下就知道怎麼作了......結果一個月下來,沒有一件事的結果能使我十分滿意。作項目的編程能力當然須要學習,但技術更替這麼快,亙古不變的是那些算法的思惟和設計的數據結構,既然決定要學,就踏踏實實從算法方面入手吧。算法
前些日子瞭解了滾動數組的求解,這幾天學習了計劃搜索。什麼是記憶搜索,就是你在當前step所作的決定影響到了你接下來的每一個決定,你必須考慮下一步的結果,使最後的價值最大化。如下經過兩個例子來詳細分析一下。(鄙人以爲,學習算法的初步就是學習例題,剛開始的時候不要急於從例題當中提取那些比較深度的思惟,只管學習,當火候到了,天然就觸類旁通,固然這其中的興趣成分十分重要。就像那個誰名人來的說過:我讀詩,並不刻意去理解這句詩是什麼意思,我只是喜歡她,我只是不斷去讀她,直到有一天,咱們相互理解)編程
1.Coins in a line : http://www.lintcode.com/en/problem/coins-in-a-line/數組
2.Coins in a line II : http://www.lintcode.com/en/problem/coins-in-a-line-ii/數據結構
對於第一題,大概題意是這樣的:給你一排硬幣,兩個足夠聰明的人交替從頭開始取這些硬幣(兩我的必須足夠聰明,假設是愛因斯坦和特斯拉吧^ ^),每次能夠取一或兩個,當取走最後哪一個硬幣的人獲勝,給你n個硬幣,問先手取的人可否獲勝。學習
按照解動態規劃的步驟來測試
一、狀態(State):當目前有n個硬幣,我要怎麼取才能保證我能取得最後一枚硬幣,咱們往前推一步,n個硬幣當中,我能夠取一枚或者兩枚,當你這一步取完的時候,對手也會執行一樣的動做(足夠聰明的對手也會考慮怎麼取才能取到最後一枚),咱們用布爾類型dp[x]來表示當目前剩下x枚硬幣且輪到你來取硬幣的時候能不能贏。這個時候咱們分兩種狀況討論:spa
①我取一枚,對手能夠取一枚或者兩枚,因此到下一輪到我取硬幣的時候,標示你可否獲勝的狀態就是dp[x-2]或dp[x-3](對手取一枚或者兩枚)。設計
②我取兩枚,對手能夠取一枚或者兩枚,到下一輪到我取硬幣的時候,標誌你可否獲勝的狀態就是dp[x-3]或dp[x-4]。3d
那也就是說,若是dp[x-2]和dp[x-3]都是true(獲勝)的話,那我在x這一步確定取一枚,這樣就能保證在dp[x]的時候爲true。爲何dp[x-2]和dp[x-3]要同時爲true才行,由於你取一枚以後,對手取完再輪到你的時候決定你是取dp[x-2]和dp[x-3]的狀況是由對手決定的,若是其中有一個爲false,那對手確定不會讓另外一個true發生。反之也成立,若是dp[x-3]和dp[x-4]同時爲true的話,那我在x這一步的時候確定取兩枚,這樣對手就不管取一枚或者兩枚都沒法阻止我獲勝啦嘿。code
如示意圖中,假設當前剩下四枚硬幣,若是我取一枚,接下來對手面對三枚,怎麼取都能讓下一步的我取到最後一枚硬幣,但若是我取兩枚,那對手足夠聰明,確定會兩枚都取而獲勝,因此我在面對四枚硬幣的時候確定取一枚。此處就至關於肯定,若是是我面對四枚硬幣,那我確定能贏,因此往上推,我只須要考慮能不能構造剩下四枚硬幣的結果就能夠了。
二、方程(Function):狀態肯定了,方程也隨之可以肯定下來。不可貴出,當剩下n枚硬幣,此時先手的人可否獲勝爲dp[n], 有
dp[n] = MemorySearch(n-2) && MemorySearch(n-3) || MemorySearch(n-3) && MemorySearch(n-4);
三、初態(Initialization):輪到我取硬幣,若是已經沒有了(n=0),那說明對手贏了,若是剩下一枚或者兩枚,那我能贏,剩下三枚,對手贏,剩下四枚,我贏。所以有
if(0 == n || 3 == n) dp[n] = false; if(1 == n|| 2 == n||4 == n) dp[n] =true
四、結果(Result): dp[n] (面對n枚硬幣先手)
代碼實現以下:傳入n枚硬幣做爲參數,返回是否可以得到勝利。
bool firstWillWin(int n) { int dp[n+1]; bool flag[n+1] = {false}; return MemorySearch(dp,n,flag); } bool MemorySearch(int *dp,int n,bool *flag){ if(true == flag[n]) return dp[n]; flag[n] = true; if(0 == n||3 == n) dp[n] = false; else if(1 == n||2==n||4==n||5==n) dp[n]= true; else{ dp[n] = ( MemorySearch(dp,n-2,flag) && MemorySearch(dp,n-3,flag) )|| ( MemorySearch(dp,n-3,flag)&&MemorySearch(dp,n-4,flag) ); } return dp[n]; }
以上是根據普通記憶搜索算法的思路簡單明瞭容易理解,但此題還能夠用數組狀態推移的方法解。肯定初態以後,之後的每一個f[x]的狀態均可以由f[x-2],f[x-3],f[x-4]得出,所以有
bool firstWillWin(int n) { bool f[n]; if(1 == n ||2==n||4 == n||5 == n) return true; if(3 == n || 0==n) return false; f[0] = true; f[1] = true; f[2] = false; f[3] = true; f[4] = true; for(int i=5;i<n;i++) f[i] = (f[i-2] && f[i-3]) || (f[i-3] && f[i-4]); return f[n-1]; }
此處f[x]表示面對的硬幣數爲x+1枚。最後的結果爲f[n-1](即n枚硬幣的狀況)。
到此,解決了給你一堆硬幣輪流取,到底會不會贏的狀況。接下來考慮一下一道拓展的題目,看第二個問題:Coins in a line II
題意大概是:給你一堆不一樣價值的硬幣,取硬幣的規則跟前者同樣,取完硬幣的時候得到價值總值高的一方勝利,問先手取的人可否獲勝。
在這種狀況下,就不是取到最後一個硬幣就可以贏得,每一步我取一枚或者兩枚都好,只要硬幣取完擁有的價值量最高。那好,當硬幣少於或者等於3枚的時候先手,那沒有疑問,取最大值價值量最高(1枚的時候取1枚,2枚或3枚的時候取2枚),固然這個時候輪到對手的時候也會這麼取,但當有四枚硬幣的時候怎麼取。舉個栗子,當前爲【1,2,4,8】的時候,我取一個,價值爲【1】,剩下【2,4,8】,通過前面的推導,剩下三枚取兩枚,結果被取走{2,4},我取最後一個【8】,這個時候價值總量爲【9】。若是我取兩個,價值爲【3(1+2)】,剩下【4,8】,必須被對手取走,此時價值總量爲【3】,比前一種狀況的價值量低,因此在剩下四枚硬幣並且此時我先手的話,確定能根據這種方法來判斷我到底取一枚仍是兩枚使最後得到的價值量最高。用dp[x]來表示當前剩下x枚硬幣的時候取到最後能取得的最大價值量,當前我取一個,下一輪我面對的價值量就是f[x-2]或f[x-3],由於對手足夠聰明,因此他確定會根據f[x-2]和f[x-3]的價值量來決定當他面對x-1個硬幣的時候是取一枚仍是兩枚,此時的話 dp[x] = min(dp[x-2],dp[x-3]) + values[x] ,此爲面對x枚硬幣取一枚的狀況。第二種狀況我取兩枚,下一輪我面對的價值量是f[x-3]或f[x-4],一樣的道理,但最後 dp[x] = min(f[x-3],f[x-4]) + values[x] + values[x+1] ,因此我到底取一枚仍是兩枚,價值量是爲 dp[x] = max( min(dp[x-2],dp[x-3])+values[x],min(dp[x-3],dp[x-4])+values[x]+values[x+1]) ,往上推一樣的道理,dp[x]的最大值取決於dp[x-2],dp[x-3],dp[x-4]的值,最後能夠得出dp[n]就是面對n枚硬幣的時候先手能夠取得最大的價值量,此時只要判斷dp[n] > sum/2便可勝出,sum爲全部硬幣的總共價值量。
代碼以下:
bool firstWillWin(vector<int> &values) { int sum = 0; int dp[values.size()+1]; bool flag[values.size()+1]; for(int i=0;i<values.size()+1;i++) flag[i] = false; for(vector<int>::iterator ite = values.begin();ite!=values.end();ite++) sum += *ite; return sum/2<MemorySearch(dp,values.size(),flag,values); } int MemorySearch(int* dp,int n,bool *flag,vector<int> values){ if(flag[n]) return dp[n]; flag[n] = true; int count = values.size(); if(0 == n) dp[n] = 0; else if(1 == n) dp[n] = values[count-n]; else if(2 == n||3 == n) dp[n] = values[count-n] + values[count-n+1]; else{ dp[n] = max( min(MemorySearch(dp,n-2,flag,values),MemorySearch(dp,n-3,flag,values))+ values[count-n], min(MemorySearch(dp,n-3,flag,values),MemorySearch(dp,n-4,flag,values))+values[count-n]+values[count-n+1] ); } return dp[n]; }
除了這個方法,還有相似第一個題目第二種解法的方法,此處留給讀者本身實現。
以上爲我的對記憶搜索算法的求解過程的理解,每一句代碼均通過本人測試可用,若有問題,但願你們提出斧正。
尊重知識產權,轉載引用請通知做者並註明出處!