硬币组合问题

来源:互联网 发布:java 动态网页开发 编辑:程序博客网 时间:2024/05/18 10:53

题目

假设我们有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;}
0 0
原创粉丝点击