經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。動態規劃經常適用於有重疊子問題和最優子結構性質的問題。html
這裏先看一道LeetCode題。從這道題來學習如何使用動態規劃。java
Coin Change算法
給定不一樣面額的硬幣 coins 和一個總金額 amount。 編寫一個函數來計算能夠湊成總金額所需的最少的硬幣個數。 若是沒有任何一種硬幣組合能組成總金額,返回 -1。
示例數據結構
輸入: coins = [1, 2, 5], amount = 11 輸出: 3 解釋: 11 = 5 + 5 + 1
該題是一個求最大最小的動態規劃算法題。ide
與遞歸解法相比,沒有重複計算。函數
肯定狀態須要有兩個注意的點:最後一步
、子問題
學習
1.最後一步spa
確定是$k$枚硬幣加起來等於11。最後一枚硬幣值假設是$a_k$,則剩下的$k-1$枚硬幣的值爲$11-a_k$。code
因爲是最優解,則11-$a_k$的硬幣數必定是最少。
2.子問題
將原問題轉換爲子問題,最少用多少枚硬幣拼出$11-a_k$
那麼$a_k$究竟是多少,由於有3枚硬幣,因此只多是一、二、5中的一個。
子問題方程以下:
$f(11) = min{f(11-1)+1,f(11-2)+1,f(11-5)+1}$
f(11)爲拼出面值爲11所需的最少硬幣數。
根據以上,使用遞歸的解法:
public class Dp1 { public int getMinCoin(int X) { if (X == 0) return 0; int res = 10000; if (X >= 1) { res = Math.min(getMinCoin(X - 1)+1, res); } if (X >= 2) { res = Math.min(getMinCoin(X - 2)+1, res); } if (X >= 5) { res = Math.min(getMinCoin(X - 5)+1, res); } return res; } public static void main(String[] args) { Dp1 dp1 = new Dp1(); int result = dp1.getMinCoin(11); System.out.println(result); } }
使用遞歸來解決,有比較多的重複計算,效率比較低。
動態規劃會保存計算結果,來避免遞歸重複計算的問題。
動態規劃的解法
狀態f[X]表示,面值爲X所需的最小硬幣數。 對於任意的X,知足 $f[X] = min{f[X-1]+1,f[X-2]+1,f[X-5]+1}$
意思就是獲取面值大小爲X最少須要的硬幣數 = 從(最後一個硬幣選1時,剩下的要湊面值爲X-1所須要的最少硬幣數,由於最後一個硬幣選了1,因此硬幣數要+1,即f[x-1]+1)、(f[X-2]+1)、(f[X-5]+1)中選擇一個最少的硬幣數。
設置初始值,考慮邊界狀況。
從上到下,從左到右。
class Solution { public int coinChange(int[] coins, int amount) { int[] f = new int[amount+1]; int coin_num = coins.length; //初始條件 f[0] = 0; //f[x] = min{f[x-c1]+1,f[x-c2]+1,f[x-c3]+1} for(int x = 1;x<=amount;x++){ f[x] = Integer.MAX_VALUE; for(int i = 0;i<coin_num;i++){ // 考慮輸入[2],4,則須要保證f[x-coins[i]] != Integer.MAX_VALUE,即f[x-coins[i]]必需要是存在的狀態 if(x >=coins[i] && f[x-coins[i]] != Integer.MAX_VALUE){ f[x] = Math.min(f[x-coins[i]]+1,f[x]); } } } // 考慮輸入[2],3,則amount = -1 if(f[amount] == Integer.MAX_VALUE){ return -1; } return f[amount]; } }
動態規劃入門 Introduction to Dynamic Programming ACM專題講解:DP動態規劃 算法數據結構面試通關(經驗全集)