動態規劃的本質不在因而遞推或是遞歸,也不須要糾結是否是內存換時間。算法
理解動態規劃並不須要數學公式介入,只是徹底解釋清楚須要點篇幅…首先須要明白哪些問題不是動態規劃能夠解決的,才能明白爲神馬須要動態規劃。不過好處時順便也就搞明白了遞推貪心搜索和動規之間有什麼關係,以及幫助那些老是把動規當成搜索解的同窗創建動規的思路。固然熟悉了以後能夠直接根據問題的描述獲得思路,若是有須要的話再補充吧。緩存
動態規劃是對於 某一類問題 的解決方法!!重點在於如何鑑定「某一類問題」是動態規劃可解的而不是糾結解決方法是遞歸仍是遞推!spa
怎麼鑑定dp可解的一類問題須要從計算機是怎麼工做的提及…計算機的本質是一個狀態機,內存裏存儲的全部數據構成了當前的狀態,CPU只能利用當前的狀態計算出下一個狀態(不要糾結硬盤之類的外部存儲,就算考慮他們也只是擴大了狀態的存儲容量而已,並不能改變下一個狀態只能從當前狀態計算出來這一條鐵律)遞歸
當你企圖使用計算機解決一個問題是,其實就是在思考如何將這個問題表達成狀態(用哪些變量存儲哪些數據)以及如何在狀態中轉移(怎樣根據一些變量計算出另外一些變量)。因此所謂的空間複雜度就是爲了支持你的計算所必需存儲的狀態最多有多少,所謂時間複雜度就是從初始狀態到達最終狀態中間須要多少步!內存
太抽象了仍是舉個例子吧:ci
好比說我想計算第100個非波那契數,每個非波那契數就是這個問題的一個狀態,每求一個新數字只須要以前的兩個狀態。因此同一個時刻,最多隻須要保存兩個狀態,空間複雜度就是常數;每計算一個新狀態所須要的時間也是常數且狀態是線性遞增的,因此時間複雜度也是線性的。element
上面這種狀態計算很直接,只須要依照固定的模式從舊狀態計算出新狀態就行(a[i]=a[i-1]+a[i-2]),不須要考慮是否是須要更多的狀態,也不須要選擇哪些舊狀態來計算新狀態。對於這樣的解法,咱們叫遞推。get
非波那契那個例子過於簡單,以致於讓人忽視了階段的概念,所謂階段是指隨着問題的解決,在同一個時刻可能會獲得的不一樣狀態的集合。非波那契數列中,每一步會計算獲得一個新數字,因此每一個階段只有一個狀態。想象另一個問題情景,假如把你放在一個圍棋棋盤上的某一點,你每一步只能走一格,由於你能夠東南西北隨便走,因此你當你一樣走四步可能會處於不少個不一樣的位置。從頭開始走了幾步就是第幾個階段,走了n步可能處於的位置稱爲一個狀態,走了這n步全部可能到達的位置的集合就是這個階段下全部可能的狀態。數學
如今問題來了,有了階段以後,計算新狀態可能會遇到各類奇葩的狀況,針對不一樣的狀況,就須要不一樣的算法,下面就分狀況來講明一下:it
假如問題有n個階段,每一個階段都有多個狀態,不一樣階段的狀態數沒必要相同,一個階段的一個狀態能夠獲得下個階段的全部狀態中的幾個。那咱們要計算出最終階段的狀態數天然要經歷以前每一個階段的某些狀態。
好消息是,有時候咱們並不須要真的計算全部狀態,好比這樣一個弱智的棋盤問題:從棋盤的左上角到達右下角最短鬚要幾步。答案很顯然,用這樣一個弱智的問題是爲了幫助咱們理解階段和狀態。某個階段確實能夠有多個狀態,正如這個問題中走n步能夠走到不少位置同樣。可是一樣n步中,有哪些位置可讓咱們在第n+1步中走的最遠呢?沒錯,正是第n步中走的最遠的位置。換成一句熟悉話叫作「下一步最優是從當前最優獲得的」。因此爲了計算最終的最優值,只須要存儲每一步的最優值便可,解決符合這種性質的問題的算法就叫貪心。若是隻看最優狀態之間的計算過程是否是和非波那契數列的計算很像?因此計算的方法是遞推。
既然問題都是能夠劃分紅階段和狀態的。這樣一來咱們一會兒解決了一大類問題:一個階段的最優能夠由前一個階段的最優獲得。
若是一個階段的最優沒法用前一個階段的最優獲得呢?
什麼你說只須要以前兩個階段就能夠獲得當前最優?那跟只用以前一個階段並無本質區別。最麻煩的狀況在於你須要以前全部的狀況才行。
再來一個迷宮的例子。在計算從起點到終點的最短路線時,你不能只保存當前階段的狀態,由於題目要求你最短,因此你必須知道以前走過的全部位置。由於即使你當前再的位置不變,以前的路線不一樣會影響你的以後走的路線。這時你須要保存的是以前每一個階段所經歷的那個狀態,根據這些信息才能計算出下一個狀態!
每一個階段的狀態或許很少,可是每一個狀態均可以轉移到下一階段的多個狀態,因此解的複雜度就是指數的,所以時間複雜度也是指數的。哦哦,剛剛提到的以前的路線會影響到下一步的選擇,這個使人不開心的狀況就叫作有後效性。
剛剛的狀況實在太廣泛,解決方法實在太暴力,有沒有哪些狀況能夠避免如此的暴力呢?
契機就在於後效性。
有一類問題,看似須要以前全部的狀態,其實不用。不妨也是拿最長上升子序列的例子來講明爲何他沒必要須要暴力搜索,進而引出動態規劃的思路。
僞裝咱們年幼無知想用搜索去尋找最長上升子序列。怎麼搜索呢?須要從頭至尾依次枚舉是否選擇當前的數字,每選定一個數字就要去看看是否是知足「上升」的性質,這裏第i個階段就是去思考是否要選擇第i個數,第i個階段有兩個狀態,分別是選和不選。哈哈,依稀出現了剛剛迷宮找路的影子!咦慢着,每次當我決定要選擇當前數字的時候,只須要和以前選定的一個數字比較就好了!這是和以前迷宮問題的本質不一樣!這就能夠縱容咱們不須要記錄以前全部的狀態啊!既然咱們的選擇已經不受以前狀態的組合的影響了,那時間複雜度天然也不是指數的了啊!雖然咱們不在意某序列以前都是什麼元素,但咱們仍是須要這個序列的長度的。因此咱們只須要記錄以某個元素結尾的LIS長度就好!所以第i個階段的最優解只是由前i-1個階段的最優解獲得的,而後就獲得了DP方程(感謝
指正)
因此一個問題是該用遞推、貪心、搜索仍是動態規劃,徹底是由這個問題自己階段間狀態的轉移方式決定的!
每一個階段只有一個狀態->遞推;
每一個階段的最優狀態都是由上一個階段的最優狀態獲得的->貪心;
每一個階段的最優狀態是由以前全部階段的狀態的組合獲得的->搜索;
每一個階段的最優狀態能夠從以前某個階段的某個或某些狀態直接獲得而無論以前這個狀態是如何獲得的->動態規劃。
每一個階段的最優狀態能夠從以前某個階段的某個或某些狀態直接獲得
這個性質叫作最優子結構;
而無論以前這個狀態是如何獲得的
這個性質叫作無後效性。
另:其實動態規劃中的最優狀態的說法容易產生誤導,覺得只須要計算最優狀態就好,LIS問題確實如此,轉移時只用到了每一個階段「選」的狀態。但實際上有的問題每每須要對每一個階段的全部狀態都算出一個最優值,而後根據這些最優值再來找最優狀態。好比揹包問題就須要對前i個包(階段)容量爲j時(狀態)計算出最大價值。而後在最後一個階段中的全部狀態種找到最優值。動態規劃迷思
本題下其餘用戶的回答跟動態規劃都有或多或少的聯繫,我也講一下與本答案的聯繫。
a. 「緩存」,「重疊子問題」,「記憶化」:
這三個名詞,都是在闡述遞推式求解的技巧。以Fibonacci數列爲例,計算第100項的時候,須要計算第99項和98項;在計算第101項的時候,須要第100項和第99項,這時候你還須要從新計算第99項嗎?不須要,你只須要在第一次計算的時候把它記下來就能夠了。
上述的須要再次計算的「第99項」,就叫「重疊子問題」。若是沒有計算過,就按照遞推式計算,若是計算過,直接使用,就像「緩存」同樣,這種方法,叫作「記憶化」,這是遞推式求解的技巧。這種技巧,通俗的說叫「花費空間來節省時間」。都不是動態規劃的本質,不是動態規劃的核心。
b. 「遞歸」:
遞歸是遞推式求解的方法,連技巧都算不上。
c. "無後效性",「最優子結構」:
上述的狀態轉移方程中,等式右邊不會用到下標大於左邊i或者k的值,這是"無後效性"的通俗上的數學定義,符合這種定義的狀態定義,咱們能夠說它具備「最優子結構」的性質,在動態規劃中咱們要作的,就是找到這種「最優子結構」。
在對狀態和狀態轉移方程的定義過程當中,知足「最優子結構」是一個隱含的條件(不然根本定義不出來)。對狀態和「最優子結構」的關係的進一步解釋,什麼是動態規劃?動態規劃的意義是什麼? - 王勐的回答 寫的很好,你們能夠去讀一下。
須要注意的是,一個問題可能有多種不一樣的狀態定義和狀態轉移方程定義,存在一個有後效性的定義,不表明該問題不適用動態規劃。這也是其餘幾個答案中出現的邏輯誤區:
動態規劃方法要尋找符合「最優子結構「的狀態和狀態轉移方程的定義,在找到以後,這個問題就能夠以「記憶化地求解遞推式」的方法來解決。而尋找到的定義,纔是動態規劃的本質。