動態規劃算法一般基於一個遞推公式及一個或多個初始狀態。 當前子問題的解將由上一次子問題的解推出。使用動態規劃來解題只須要多項式時間複雜度, 所以它比回溯法、暴力法等要快許多。動態規劃也是面試筆試題中的一個考查重點,當閱讀一個題目而且開始嘗試解決它時,首先看一下它的限制。 若是要求在多項式時間內解決,那麼該問題就極可能要用DP來解。遇到這種狀況, 最重要的就是找到問題的「狀態」和「狀態轉移方程」。(狀態不是隨便定義的, 通常定義完狀態,你要找到當前狀態是如何從前面的狀態獲得的, 即找到狀態轉移方程)若是看起來是個DP問題,但你卻沒法定義出狀態, 那麼試着將問題規約到一個已知的DP問題。java
這裏先說明一個最簡單的動態規劃實例:硬幣問題。後續還會給出更多的實例,例如:最長公共子序列,最長公共子串,最長遞增子序列,字符串編輯距離等。動態規劃的關鍵就是找出「狀態」和「狀態轉移方程」。面試
硬幣問題:給你一些面額的硬幣,而後給你一個值N,要你求出構成N所須要的最少硬幣的數量和方案。分析:這個問題能夠嘗試用貪心算法去解決,先從面額最大的硬幣開始嘗試,一直往下找,知道硬幣總和爲N。可是貪心算法不能保證可以找出解(例如,給,2,3,5,而後N=11)。咱們能夠換個思路,咱們用d(i)表示求總和爲i的最少硬幣數量(其實就是動態規劃中的「狀態」),那麼怎麼從前面的狀態(並不必定是d(i-1)這一個狀態)到d(i)這個狀態?假設硬幣集合爲coins[0~N],在求d(i)以前,咱們假設d(1~i-1)所有都求出來了,那麼d(i)=min{d(j)+1},if i-j 在coins中(其實這就是「狀態轉移方程」)。舉例說明:coins={2,3,5},N=11。算法
d(0)=0;code
d(1)=0;字符串
d(2)=d(0)+1=1;io
d(3)=d(0)+1=1;class
d(4)=d(2)+1=2;筆試
d(5)=min{d(3)+1,d(2)+1,d(0)+1}=1;static
d(6)=min{d(4)+1,d(3)+1}=2;集合
.......................
同時爲了求出最後的方案(不單單是硬幣個數),須要記錄求每一個狀態選擇的「路徑」,例如:求d(5)咱們選擇了d(0)+1,那麼咱們選擇的路徑就是5-0=5。咱們必須記錄這些路徑,而後根據路徑得出結果。對於d(6),咱們開始選擇了3,也就是說咱們選擇了從d(3)狀態和硬幣3跳轉到d(6),接着對於d(3),咱們選擇了3,也就是說咱們選擇了從d(0)狀態和硬幣3跳轉到了d(3),接着對於d(0),這個是初始狀態。因此咱們的方案是3,3。
若是上面說得還不夠清晰,能夠參照下面JAVA實現的代碼
/** * * @author kerry * 給定製定面值的硬幣 ,並給出一個值,要求求出硬幣綜合爲這個值須要的最少的硬幣的個數,並具體的方案 */ public class MinCoins { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int[] coins={1,3,5}; int value=100; int[] solu=new int[value]; int min=new MinCoins().solution(coins,value,solu); for(int i=value-1;i>=0;){ System.out.print(solu[i]+"->"); i=i-solu[i]; } System.out.println(); System.out.println(min); } private int solution(int[] coins,int value, int[] solu){ int[] mins = new int[value+1]; mins[0]=0; for(int i=1;i<=value;i++) mins[i]=Integer.MAX_VALUE; for(int i=1;i<=value;i++){ for(int j=0;j<coins.length;j++){ if(coins[j]<=i&&mins[i]>mins[i-coins[j]]+1){ mins[i]=mins[i-coins[j]]+1; solu[i-1]=coins[j]; } } } return mins[value]; } }