1. 程式人生 > >動態規劃演算法求解硬幣找零問題

動態規劃演算法求解硬幣找零問題


硬幣找零問題描述:現存在一堆面值為 V1、V2、V3 … 個單位的硬幣,問最少需要多少個硬幣才能找出總值為 T 個單位的零錢?假設這一堆面值分別為 1、2、5、21、25 元,需要找出總值 T 為 63 元的零錢。

很明顯,只要拿出 3 個 21 元的硬幣就湊夠了 63 元了。

基於上述動態規劃的思想,我們可以從 T=1 開始計算出最少需要幾個硬幣,然後再求 T=2 、T=3…每一次求得的結果都儲存在一個數組中,以後需要用到時則直接取出即可。

從 T=1 開始依次找零時,可以嘗試一下當前要找零的面值 T 是否能夠被分解成 另一個已求解的面值的找零需要的硬幣個數 再加上 這一堆硬幣中的某個面值之和,如果這樣分解之後最終的硬幣數是最少的,那麼問題就得到答案了。

單是上面的文字描述太抽象,先假定以下變數:

values[] : 儲存每一種硬幣的幣值的陣列
valueKinds :幣值不同的硬幣種類數量,即values[]陣列的大小
money : 需要找零的面值
dp[] : 儲存面值為 i 的紙幣找零所需的最小硬幣數

演算法描述:

當求解總面值為 i 的找零最少硬幣數 dp[i] 時,將其分解成求解 dp[i – cents] 和一個面值為 cents 元的硬幣,由於 i – cents < i , 其解 dp[ i – cents] 已經在之前的迴圈中被算出,如果面值為 cents 的硬幣滿足題意,那麼最終解 dp[i] 則等於 dp[i – cents] 再加上 1(即面值為 cents)的這一個硬幣。

public class CoinsChange {
	/**
	 * 硬幣找零:動態規劃演算法
	 * @param values:儲存每一種硬幣的幣值的陣列        
	 * @param valueKinds:幣值不同的硬幣種類數量,即coinValue[]陣列的大小         
	 * @param money:需要找零的面值       
	 * @param dp:儲存面值為i的紙幣找零所需的最小硬幣數          
	 */
	public static void makeChange(int[] values, int valueKinds, int money, int[] dp) {
		dp[0] = 0;
		// 對每一分錢都找零,即儲存子問題的解以備用,即填表
		for (int cents = 1; cents <= money; cents++) {
			// 當用最小幣值的硬幣找零時,所需硬幣數量最多
			int minCoins = cents;
			// 遍歷每一種面值的硬幣,看是否可作為找零的其中之一
			for (int kind = 0; kind < valueKinds; kind++) {
				// 若當前面值的硬幣小於當前的cents則分解問題並查表
				if (values[kind] <= cents) {
					int temp = dp[cents - values[kind]] + 1;
					if (temp < minCoins) {
						minCoins = temp;
					}
				}
			}
			// 儲存最小硬幣數
			dp[cents] = minCoins;
			System.out.println("面值為 " + (cents) + " 的最小硬幣數 : " + dp[cents]);
		}
	}

	public static void main(String[] args) {
		// 硬幣面值預先已經按降序排列
		int[] coinValue = new int[] { 25, 21, 10, 5, 1 };
		// 需要找零的面值
		int money = 63;
		// 儲存每一個面值找零所需的最小硬幣數,0號單元捨棄不用,所以要多加1
		int[] dp = new int[money + 1];
		makeChange(coinValue, coinValue.length, money, dp);
	}
}
上面的程式碼並沒有給出具體應該是哪幾個面值的硬幣,這個可以再使用一些陣列儲存而打印出來。