動態規劃的本質
常用的五大演算法,包含 動態規劃、分治法、貪心求解法、回朔法、分支限界法。
動態規劃(Dynamic Programming),與其說是一種演算法,不如說是一種解決問題的思路。 :peach:
Dynamic Programming is a methed for solving a complex problem by breaking it down into a collection of simpler subproblems.
上述引自維基百科,也就是說動態規劃就是將一個複雜的問題分解成若干簡單的問題集的一種方法。
那麼怎麼分解問題就成了動態規劃的本質。
而分解問題,依靠的就是 問題的狀態 和 狀態之間的轉移。
- 如何定義一個狀態
我們需要找到一個問題在某一個狀態的 最優解。 :strawberry:
舉個例子:
最長遞增子序列(LIS) 給定一個長度為N的陣列,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂) 例如:給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8} 則其最長的單調遞增子序列為{5,6,7,8},長度為4
由此我們定義一個狀態,當以第i個陣列元素結尾的最長遞增子序列dp(i)。 整個陣列的LIS就是dp(1)...dp(n)的最大值。
由此我們就定義好了一個問題的狀態,下面我們就看看不同狀態之間的轉移。
- 如何找到狀態的轉移
首先,我們需要找到狀態的 邊界值。 :watermelon:
根據上述LIS的問題,邊界值為當i=1時,最長遞增子序列為1。
然後,我們需要找到狀態之間的 關係。 :banana:
dp(i) = max(1,dp(j)+1...) (0<=j<i) 當array[j]<array[i]
解釋一下,在保證第i項比第j項大的情況下,要取之前所有項的最長遞增子序列加1的最大值。
這裡可以看出,這裡的狀態轉移方程,就是定義了問題和子問題之間的關係。 可以看出,狀態轉移方程就是帶有條件的遞推式。
動態規劃經典練習
這裡羅列了6道比較經典的動態規劃練習。 :corn:
/**
* No1.塔樹選擇和最大問題
*
* 一個高度為N的由正整陣列成的三角形,從上走到下,求經過的數字和的最大值。
* 每次只能走到下一層相鄰的數上,例如從第3層的6向下走,只能走到第4層的2或9上。
* 5
* 8 4
* 3 6 9
* 7 2 9 5
* 例子中的最優方案是:5 + 8 + 6 + 9 = 28
*
* 輸入:符合塔樹的二維陣列。
* 輸出:經過的最大值。
*/
/**
* No2.∑乘法表問題
*
* ∑ | a b c
* ——————————————
* a | b b a
* b | c b a
* c | a c c
*
* 依此乘法表,對任一定義於∑上的字串,適當加括號表示式後得到一個表示式。
* 例如,對於字串x=bbbba,它的其中一個加括號表示式為i(b(bb))(ba)。
* 依乘法表,該表示式的值為a。試設計一個動態規劃演算法,對任一定義於∑上的字串x=x1x2…xn。
* 計算有多少種不同的加括號方式,使由x匯出的加括號表示式的值為a。
*
* 輸入:輸入一個以a,b,c組成的任意一個字串。
* 輸出:計算出的加括號方式數。
*/
/**
* No.3跳臺階
*
* 一隻青蛙一次可以跳上1級臺階,也可以跳上2級。
* 求該青蛙跳上一個n級的臺階總共有多少種跳法。
*
* 輸入:臺階數n。
* 輸出:跳法總數。
*/
/**
* No.4最長遞增子序列(LIS)
*
* 給定一個長度為N的陣列,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。
* 例如:給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8}。
* 則其最長的單調遞增子序列為{5,6,7,8},長度為4。
*
* 輸入:一個陣列。
* 輸出:最長遞增子序列的長度。
*/
/**
* No.5揹包問題
*
* 有N件物品和一個容量為V的揹包。
* 第i件物品的大小是c[i],價值是w[i]。
* 求解將哪些物品裝入揹包可使價值總和最大。
*
* 輸入:物品大小陣列c,物品價值陣列w,揹包容量。
* 輸出:最大的價值。
*/
/**
* No.6最長公共子序列(LCS)
*
* 給出兩個字串a, b,求它們的最長的公共子序列。
*
* 輸入:字串a和字串b。
* 輸出:最長的公共子序列的長度。
*/
複製程式碼
請先根據動態規劃的本質,定義出 狀態 和 狀態之間的關係,然後再進行程式碼編寫。
如果你已經完成了練習,這裡有上述問題的答案,戳這裡。