硬幣組合問題

題目

假設我們有8種不同面值的硬幣{1,2,5,10,20,50,100,200},用這些硬幣組合夠成一個給定的數值n。例如n=200,那麼一種可能的組合方式爲 200 = 3 * 1 + 1*2 + 1*5 + 2*20 + 1 * 50 + 1 * 100. 問總過有多少種可能的組合方式?原題 轉載

分析

這道題目是非常經典的動態規劃算法題。給定一個數值sum,假設我們有m種不同類型的硬幣v1,v2,...,vm ,如果要組合成sum,那麼我們有
sum=x1v1+x2v2+...+xmvm
求所有可能的組合數,就是求滿足前面等值的係數x1,x2,...,xm 的所有可能個數。

思路1:
用暴力枚舉,各個係數可能的取值無非是
x1=0,1,...,sumv1,x2=0,1,...,sumv2
,這對於硬幣種類數較小還可以應付。
思路2:
從上面的分析中我們也可以這麼考慮,我們希望用m種硬幣構成sum,根據最後一個硬幣vm 的係數的取值爲無非有這麼幾種情況,xm 分別取0,1,2,...,sumvm 。即,
sum=x1v1+x2v2+...+0vm
sum=x1v1+x2v2+...+1vm
sum=x1v1+x2v2+...+2vm

sum=x1v1+x2v2+...+kvm
其中k=sumvm
定義dp[i][sum] = 用前i種硬幣構成sum 的所有組合數。
  那麼題目的問題實際上就是求dp[m][sum],即用前m種硬幣(所有硬幣)構成sum的所有組合數。
  在上面的聯合等式中,當xm=0 時,有多少種組合呢? 實際上就是前i-1種硬幣組合sum,有dp[i-1][sum]種!
  xm=1 時呢,有多少種組合? 實際上是用前i-1種硬幣組合成(sum - Vm)的組合數,有dp[i-1][sum -Vm]種; xn =2呢, dp[i-1][sum - 2 * Vm]種。所有的這些情況加起來就是我們的dp[i][sum]。
dp[i][sum]=dp[i1][sum0vm]+dp[i1][sum1vm]+dp[i1][sum2vm]++dp[i1][sumkvm]
其中k=sumvm
初始情況:如果sum=0,那麼無論有前多少種來組合0,只有一種可能,就是各個係數都等於0,
dp[i][0]=1i=0,1,2,...,m
如果我們用二位數組表示dp[i][sum], 我們發現第i行的值全部依賴與i-1行的值,所以我們可以逐行求解該數組。如果前0種硬幣要組成sum,我們規定爲dp[0][sum] = 0。
思路3:硬幣組合問題,本質上就是組合數的問題,解決組合問題,非常經典的算法是回溯算法,它在無限的解空間中深度優先搜索。

第一種動態規劃算法實現如下:

import java.util.Scanner;

/**
 * 有幾種紙幣面值1, 5, 10, 20, 50, 100元,假設每種面值的紙幣無限,用它們組合成N元。找出所有的組合數。
 * @author ShaoCheng
 * @version 1.0 2015-9-19
 */
public class Solution {
    /**
     * @param N 輸入的總和N
     * @return 所有的組合數
     */
    public long getNumberOfCombinations(int N) {
        int coinKinds = coins.length;
        int[][] dp = new int[coinKinds+1][N+1];

        for(int i = 0; i <= coinKinds; i++){ //初始化
            for(int j = 0; j <= N; ++j){
                dp[i][j] = 0;
            }
        }

        for(int i = 0; i <= coinKinds; i++){
            dp[i][0] = 1;//前i種紙幣組合成0,只有一種情況就是個數均爲0
        }

        for(int i = 1; i <= coinKinds; i++){
            for(int j = 1; j <= N; j++){
                dp[i][j] = 0;
                for(int k = 0; k <= j / coins[i-1]; k++){
                    dp[i][j] += dp[i-1][j - k*coins[i-1]];
                }
            }
        }
        return dp[coinKinds][N];
    }

    public static void main(String[] args){
        Solution sl = new Solution();
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        long res = sl.getNumberOfCombinations(N);
        System.out.println(res);
        scanner.close();
    }

    public Solution() {
        // TODO Auto-generated constructor stub
        coins = new int[]{1, 5, 10, 20, 50, 100};
    }
    private int[] coins;
}

回溯算法實現如下:

import java.util.Arrays;
import java.util.Scanner;

/**
 * 有幾種紙幣面值1, 5, 10, 20, 50, 100元,假設每種面值的紙幣無限,用它們組合成N元。找出所有的組合數。
 * @author ShaoCheng
 * @version 1.1 2015-9-20
 */
public class Solution {
    /**
     * @param N 輸入的總和N
     * @return 所有的組合數
     */
    public long getNumberOfCombinations(int N) {
        long sum = 0;
        return getNumberOfCombinations(N, sum, 0);
    }

    public long getNumberOfCombinations(int N, long sum, int start){
        long count = 0;
        for(int i = start; i < coins.length; i++){
            sum += coins[i];
            if(sum == N){
                count++;
                break;
            }
            if(sum < N){
                count += getNumberOfCombinations(N, sum, i);
                sum -= coins[i];
            }
            else
                break;
        }
        return count;
    }

    public static void main(String[] args){
        Solution sl = new Solution();
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        long res = sl.getNumberOfCombinations(N);
        System.out.println(res);
        scanner.close();
    }

    public Solution() {
        // TODO Auto-generated constructor stub
        coins = new int[]{1, 5, 10, 20, 100, 50}; //如果亂序,應先排序
        Arrays.sort(coins);
    }
    private int[] coins;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章