1. 程式人生 > >動態規劃(其一)

動態規劃(其一)

重點 序列 href 如何 本質 相同 影響 好處 每次

作者:王猛
鏈接:https://www.zhihu.com/question/23995189/answer/35429905
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。

動態規劃的本質不在於是遞推或是遞歸,也不需要糾結是不是內存換時間。

理解動態規劃並不需要數學公式介入,只是完全解釋清楚需要點篇幅…首先需要明白哪些問題不是動態規劃可以解決的,才能明白為神馬需要動態規劃。不過好處時順便也就搞明白了遞推貪心搜索和動規之間有什麽關系,以及幫助那些總是把動規當成搜索解的同學建立動規的思路。當然熟悉了之後可以直接根據問題的描述得到思路,如果有需要的話再補充吧。

動態規劃是對於 某一類問題 的解決方法!!重點在於如何鑒定“某一類問題”是動態規劃可解的而不是糾結解決方法是遞歸還是遞推!

怎麽鑒定dp可解的一類問題需要從計算機是怎麽工作的說起…計算機的本質是一個狀態機,內存裏存儲的所有數據構成了當前的狀態,CPU只能利用當前的狀態計算出下一個狀態(不要糾結硬盤之類的外部存儲,就算考慮他們也只是擴大了狀態的存儲容量而已,並不能改變下一個狀態只能從當前狀態計算出來這一條鐵律)


當你企圖使用計算機解決一個問題是,其實就是在思考如何將這個問題表達成狀態(用哪些變量存儲哪些數據)以及如何在狀態中轉移(怎樣根據一些變量計算出另一些變量)。所以所謂的空間復雜度就是為了支持你的計算所必需存儲的狀態最多有多少,所謂時間復雜度就是從初始狀態到達最終狀態中間需要多少步!

太抽象了還是舉個例子吧:

比如說我想計算第100個非波那契數,每一個非波那契數就是這個問題的一個狀態,每求一個新數字只需要之前的兩個狀態。所以同一個時刻,最多只需要保存兩個狀態,空間復雜度就是常數;每計算一個新狀態所需要的時間也是常數且狀態是線性遞增的,所以時間復雜度也是線性的。

上面這種狀態計算很直接,只需要依照固定的模式從舊狀態計算出新狀態就行:

a[i]=a[i-1]+a[i-2],

不需要考慮是不是需要更多的狀態,也不需要選擇哪些舊狀態來計算新狀態。對於這樣的解法,我們叫遞推。

非波那契那個例子過於簡單,以至於讓人忽視了階段的概念,所謂階段是指隨著問題的解決,在同一個時刻可能會得到的不同狀態的集合。非波那契數列中,每一步會計算得到一個新數字,所以每個階段只有一個狀態。想象另外一個問題情景,假如把你放在一個圍棋棋盤上的某一點,你每一步只能走一格,因為你可以東南西北隨便走,所以你當你同樣走四步可能會處於很多個不同的位置。從頭開始走了幾步就是第幾個階段,走了n步可能處於的位置稱為一個狀態,走了這n步所有可能到達的位置的集合就是這個階段下所有可能的狀態。

現在問題來了,有了階段之後,計算新狀態可能會遇到各種奇葩的情況,針對不同的情況,就需要不同的算法,下面就分情況來說明一下:


假如問題有n個階段,每個階段都有多個狀態,不同階段的狀態數不必相同,一個階段的一個狀態可以得到下個階段的所有狀態中的幾個。那我們要計算出最終階段的狀態數自然要經歷之前每個階段的某些狀態。

好消息是,有時候我們並不需要真的計算所有狀態,比如這樣一個弱智的棋盤問題:從棋盤的左上角到達右下角最短需要幾步。答案很顯然,用這樣一個弱智的問題是為了幫助我們理解階段和狀態。某個階段確實可以有多個狀態,正如這個問題中走n步可以走到很多位置一樣。但是同樣n步中,有哪些位置可以讓我們在第n+1步中走的最遠呢?沒錯,正是第n步中走的最遠的位置。換成一句熟悉話叫做“下一步最優是從當前最優得到的”。所以為了計算最終的最優值,只需要存儲每一步的最優值即可,解決符合這種性質的問題的算法就叫貪心。如果只看最優狀態之間的計算過程是不是和非波那契數列的計算很像?所以計算的方法是遞推。

既然問題都是可以劃分成階段和狀態的。這樣一來我們一下子解決了一大類問題:一個階段的最優可以由前一個階段的最優得到。

如果一個階段的最優無法用前一個階段的最優得到呢?

什麽你說只需要之前兩個階段就可以得到當前最優?那跟只用之前一個階段並沒有本質區別。最麻煩的情況在於你需要之前所有的情況才行。

再來一個迷宮的例子。在計算從起點到終點的最短路線時,你不能只保存當前階段的狀態,因為題目要求你最短,所以你必須知道之前走過的所有位置。因為即便你當前再的位置不變,之前的路線不同會影響你的之後走的路線。這時你需要保存的是之前每個階段所經歷的那個狀態,根據這些信息才能計算出下一個狀態!

每個階段的狀態或許不多,但是每個狀態都可以轉移到下一階段的多個狀態,所以解的復雜度就是指數的,因此時間復雜度也是指數的。哦哦,剛剛提到的之前的路線會影響到下一步的選擇,這個令人不開心的情況就叫做有後效性。

剛剛的情況實在太普遍,解決方法實在太暴力,有沒有哪些情況可以避免如此的暴力呢?

契機就在於後效性。

有一類問題,看似需要之前所有的狀態,其實不用。不妨也是拿最長上升子序列的例子來說明為什麽他不必需要暴力搜索,進而引出動態規劃的思路。

假裝我們年幼無知想用搜索去尋找最長上升子序列。怎麽搜索呢?需要從頭到尾依次枚舉是否選擇當前的數字,每選定一個數字就要去看看是不是滿足“上升”的性質,這裏第i個階段就是去思考是否要選擇第i個數,第i個階段有兩個狀態,分別是選和不選。哈哈,依稀出現了剛剛迷宮找路的影子!咦慢著,每次當我決定要選擇當前數字的時候,只需要和之前選定的一個數字比較就行了!這是和之前迷宮問題的本質不同!這就可以縱容我們不需要記錄之前所有的狀態啊!既然我們的選擇已經不受之前狀態的組合的影響了,那時間復雜度自然也不是指數的了啊!雖然我們不在乎某序列之前都是什麽元素,但我們還是需要這個序列的長度的。所以我們只需要記錄以某個元素結尾的LIS長度就好!因此第i個階段的最優解只是由前i-1個階段的最優解得到的,然後就得到了DP方程(感謝

@韓曦 指正)
技術分享圖片

所以一個問題是該用遞推、貪心、搜索還是動態規劃,完全是由這個問題本身階段間狀態的轉移方式決定的!

每個階段只有一個狀態->遞推;
每個階段的最優狀態都是由上一個階段的最優狀態得到的->貪心;
每個階段的最優狀態是由之前所有階段的狀態的組合得到的->搜索;
每個階段的最優狀態可以從之前某個階段的某個或某些狀態直接得到而不管之前這個狀態是如何得到的->動態規劃。

每個階段的最優狀態可以從之前某個階段的某個或某些狀態直接得到

這個性質叫做最優子結構;

而不管之前這個狀態是如何得到的

這個性質叫做無後效性。

另:其實動態規劃中的最優狀態的說法容易產生誤導,以為只需要計算最優狀態就好,LIS問題確實如此,轉移時只用到了每個階段“選”的狀態。但實際上有的問題往往需要對每個階段的所有狀態都算出一個最優值,然後根據這些最優值再來找最優狀態。比如背包問題就需要對前i個包(階段)容量為j時(狀態)計算出最大價值。然後在最後一個階段中的所有狀態種找到最優值。

動態規劃(其一)