動態規劃是解決一些特定類型的在多項式時間問題的技術。動態編程解決方案比指數暴力方法更快,而且能夠很容易地證實它們的正確性。在咱們研究如何動態思考問題以前,咱們須要知道如下兩點:編程
重疊的子問題bash
最優子結構性質優化
一般,能夠經過使用動態編程來解決須要最大化或最小化某些數量或計數問題的全部問題,這些問題用於計算在特定條件下的排列或某些機率問題。ui
全部動態編程問題都知足重疊子問題屬性,大多數經典動態問題也知足最優子結構屬性。有時咱們在給定的問題中觀察這些屬性,確保它可使用動態規劃思路來解決。spa
動態規劃問題都與狀態及其轉移有關。這是必須很是細心完成的最基本的步驟,由於狀態轉移取決於所作的狀態定義的選擇。那麼,讓咱們看看狀態究竟是什麼意思。code
狀態 :一個狀態能夠定義爲一組的能夠惟一識別的特定位置,或在給定的問題中的參數。這組參數應儘量小,以減小狀態空間。索引
例如:在著名的揹包問題中,經過兩個參數索引和權重來定義狀態,即pack[index] [weight]。這裏pack[index] [weight]告訴咱們經過從0到具備袋重量的索引的項目能夠得到的最大利潤。所以,這裏參數索引和權重一塊兒能夠惟一地識別揹包問題的子問題。內存
所以,咱們的第一步是在肯定問題是DP問題後決定問題的狀態。咱們知道DP就是使用計算結果來制定最終結果,因此下一步將是找到先前狀態之間的關係以達到當前狀態。leetcode
這裏舉個例子,藉助leetcode上面的習題來吧,下面是問題的詳細內容:class
假設你正在爬樓梯。須要 n 階你才能到達樓頂。
每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法能夠爬到樓頂。
1. 1 階 + 1 階
2. 2 階
示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法能夠爬到樓頂。
1. 1 階 + 1 階 + 1 階
2. 1 階 + 2 階
3. 2 階 + 1 階
複製代碼
接下來用動態規劃的方式思考這個問題。首先,決定給定問題的狀態,將採用參數n來決定狀態,由於它能夠惟一地識別任何子問題,接下來將用T(n)來表示一個階段的狀態變量。
如今,咱們須要計算T(n)。到底該怎麼來計算呢?注意看裏面的括號,其表明上一個子結果的狀態量。
1. 1階
很明顯只有一層階梯,因此T(1) = 1
複製代碼
1. (1 階) + 1 階
2. 2 階
因此T(2) = 2
複製代碼
1. (1 階 + 1 階) + 1 階
2. (1 階) + 2 階
3. (2 階) + 1 階
T(3) = 3
複製代碼
1. (1 階 + 1 階 + 1 階) + 1階
2. (1 階 + 2 階) + 1階
3. (2 階 + 1 階) + 1階
4. (1階 + 1 階) + 2階
5. (2階)+ 2階
T(4) = 5
複製代碼
如今再仔細去思考其中的問題,有沒有看到對應的關係,就拿第四層臺階來講吧,根據上一次臺階能夠知道要麼是前一層臺階上1階,要麼是前兩層上2階
T(4) = T(3) + T(2)
因此用參數代表就能夠獲得下面的狀態轉移表達式
T(n) = T(n-1) + T(n-2) 需知足n>2
下面用代碼實現編寫表達式的實現。
func climbStairs(n int) int{
if n == 1 {
return 1
}
if n == 2{
return 2
}
return climbStairs(n-1)+climbStairs(n-2)
}
複製代碼
這是動態編程解決方案中最簡單的部分。由於以前的狀態計算是呈指數增加,因此咱們須要存儲狀態變量,以便下次須要該狀態時,能夠直接在內存中使用它。 用表明實現示例以下:
func climbStairs(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
memo := make(map[int]int)
memo[1] = 1
memo[2] = 2
for i := 3; i <= n; i++ {
memo[i] = memo[i-1] + memo[i-2]
}
return memo[n]
}
複製代碼