1. 程式人生 > >動態規劃入門之硬幣問題

動態規劃入門之硬幣問題

動態規劃演算法通常基於一個遞推公式及一個或多個初始狀態。 當前子問題的解將由上一次子問題的解推出。使用動態規劃來解題只需要多項式時間複雜度, 因此它比回溯法、暴力法等要快許多。動態規劃也是面試筆試題中的一個考查重點,當閱讀一個題目並且開始嘗試解決它時,首先看一下它的限制。 如果要求在多項式時間內解決,那麼該問題就很可能要用DP來解。遇到這種情況, 最重要的就是找到問題的“狀態”和“狀態轉移方程”。(狀態不是隨便定義的, 一般定義完狀態,你要找到當前狀態是如何從前面的狀態得到的, 即找到狀態轉移方程)如果看起來是個DP問題,但你卻無法定義出狀態, 那麼試著將問題規約到一個已知的DP問題。

這裡先說明一個最簡單的動態規劃例項:硬幣問題。後續還會給出更多的例項,例如:最長公共子序列,最長公共子串,最長遞增子序列,字串編輯距離等。動態規劃的關鍵就是找出“狀態”和“狀態轉移方程”。

硬幣問題:給你一些面額的硬幣,然後給你一個值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;

d(1)=0;

d(2)=d(0)+1=1;

d(3)=d(0)+1=1;

d(4)=d(2)+1=2;

d(5)=min{d(3)+1,d(2)+1,d(0)+1}=1;

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];
	}

}
如果錯誤,還請多多指出~