硬币组合问题

题目

假设我们有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;
}

猜你喜欢

转载自blog.csdn.net/psc928624742/article/details/48576447