在吃了幾次DP的虧以後,我終于開始着手準備DP的博文了……
首先推薦一篇圖文:
轉自程式員小灰,侵删
圖文所使用的JAVA代碼我已經将其轉換為了CPP代碼并貼于文末,有興趣自取~~
首先讓我們來了解一下,什麼是動态規劃
動态規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程(multistep decision process)的優化問題時,提出了著名的最優化原理(principle of optimality),把多階段過程轉化為一系列單階段問題,利用各階段之間的關系,逐個求解,創立了解決這類過程優化問題的新方法——動态規劃 ——引用自百度百科
動态規劃題目的特點:
1.計數有多少種方式走到右下角
有多少種方法挑選出K個數使得和為sum
……
2.求最大最小值從左上角走到右下角路徑的最大數字和
最長上升子序列長度
……
3.求存在性取石子遊戲,先手是否獲勝
能不能選出K個數使得和為sum
……
但是如果要求輸出所有的情況,則不是動态規劃的題目,此時應當考慮深搜或者疊代
動态規劃的題目沒有固定的模闆(變化比較多),一般難度比較高,屬于中上難度,是必須要掌握的算法
動态規劃有三個重要的概念:最優子結構:問題的最優解由相關子問題的最優解集合組成
邊界:問題的邊界,得到有限的結果
狀态轉移公式:問題每一階段和下一階段的關系
動态規劃解題步驟:
說明:這裡我們直接借用侯老師的視訊給大家解釋,因為視訊時間較長,我給大家總結了關鍵點
首先引入一個題目:
我有十級台階,每次可以上一級或者兩級,我上完整個台階有多少鐘方法?
看完這個題目,你或許想的是用排列組合的思想,寫一個多層的虛幻周遊所有的情況然後統計最後的答案吧,但是這種算法所需的時間複雜度确實太大O(n2),那有沒有更優秀的算法呢?
當然有!
有請今天的主角——動态規劃
要寫出動态規劃的題,我們需要如下3個步驟
1.确定狀态
确定狀态需要兩個意識:最後一步
子問題
我們首先來确定最後一步
在剛剛列出的問題中,最後一步有兩種走法:
走一步和走兩步,這兩種走法都是固定的
然後我們再來看看,假設走到第九級有X種走法,走到第八級有Y種走法
那麼,走到第十級有多少種走法呢?
哈哈,當然是X+Y種啦!因為走到第九級的時候,就隻有一種走法——走一步了
走到第八級的時候,也隻能走兩步,因為如果走一步的話,就包含在走到九級的情況中了
是以走到十級的情況隻有X+Y
2.轉移方程
現在,我們把走到第十級的方法數表示為F(10)
走到第九級和第八級的方法數分别表示為F(9),F(8)
是以有F(10)=F(9)+F(8)
同理,有F(9)=F(8)+F(7),F(8)=F(7)+F(6)……
于是我們歸納出如下一個公式:
F(n)=F(n-1)+F(n-2)
這就是這道題階段與階段間的狀态轉移方程,決定了問題的每一個階段和下一個階段的關系
3.初始條件和邊界情況
但是當我們計算到最後一級或者兩級台階的時候,我們便無需繼續計算,于是F(1)和F(2)便是問題的邊界,但是如果一個問題沒有邊界,那麼我們永遠無法得到一個有限的結果
解決了如上三個問題,接下來便是寫代碼了
或許你會說,噫,不就是個遞歸麼,好辦!但是如果仔細分析,你會發現,如果使用遞歸,時間複雜度依舊是O(n2),這就不是我們費勁來尋找更快的算法的理由的,在這個遞歸中,出現了大量的重複計算,比如F(10)=F(9)+F(8),F(9)=F(8)+F(7),F(8)=F(7)+F(6),裡面的F(8)便被計算了兩次,當F(n)中n的值足夠大時,裡面重複計算的就更多,也許聰明的你很快就想到了使用備忘錄算法來解決這件事,但是使用備忘錄算法的空間複雜度卻有O(n),這也是一個比較讓人頭疼的事情
那麼……有沒有更好的解決辦法呢?
突然,一個小夥伴發現,這個……莫非解釋傳說中的肥波納妾數列?(斐波那契數列)
是的,按照斐波那契數列的計算方式,我們可以很快的列出式子來解決這個問題int get_climbing_way(int n){
if (n<1) return 0;
if (n==1) return 1;
if (n==2) return 2;
int a=1,b=2,tmp=0;
for (int i=3;i<=n;i++){
tmp=a+b;
a=b;
b=tmp;
}
return tmp;
}
程式從i=3開始疊代,一直到i=n結束,時間複雜度O(n),空間複雜度O(1),是不是很便捷的算法呢?
當然,本文隻是粗略的解析了一下動态規劃,因為DP的變數實在太多,還是應當多加練題才能徹底參透DP
例題:
番外個體
1.圖文代碼翻譯:備忘錄算法int get_climbing_ways(int n){
if (n<1) return 0;
if (n==1) return 1;
if (n==2) return 2;
if (s.count(n)) return s[n];
int value=get_climbing_ways(n-1)+get_climbing_ways(n-2);
s[n]=value;
return value;
}
2.最後給個答案吧:89
若未經特殊說明,本文:《 粗析動态規劃DP 》為部落客原創作品,未經授權禁止轉載