第七章 递归、DFS、剪枝、回溯等问题 ------------- 7.4 硬币表示某个给定数值

一、题目:

  假设我们有8种不同面值的硬币{1,5,10,25},用这些硬币组合构成一个给定的数值n。 例如n=100,那么一种可能的组合方式为 100 = 2*25+5*5+2*10+5*1。 问总共有多少种可能的组合方式?

二、思路:

  先来看看递归解法,看到这种题目,不要一上来就想着我怎么来划分任务,我怎么把任务交给其他人去做。首先还是由简单到复杂,先列举一些简单的情况,发现规律。因为涉及到动态改变的问题,多种组合的方式,我们可以先在纸上写出几个简单的例子来进行分析,我们不妨写出1-25这个区间看一下n = 1-25之间的组合方案。

1---1
2---1
3---1
4---1
5---2
6---2
7---2
8---2
9---2
10---4
11---4
12---4
13---4
14---4
15---6 ...

  因为给出的n直接牵涉到能够使用较大的面值 比如n = 15 它就不能够使用25的面值,只能从10 5 1这三种面值来组合。比如n = 25 我们可以使用0个25的面值而剩下的由10 5 1来进行组合, 10 可以由使用10 5 1来组合, 5可以由 5 1来组合,比如n=100,我们可以使用0个25,剩下的100交给 10 5 1去凑,也可以使用1个25,那么剩下的75也交给10 5 1去凑,也可以使用2个25,3个25,这样基本就是递归的思路了。因为涉及到两个变量在变化,我们可以在循环中使用递归来解决,其中递归的方法需要解决传入的变量的问题:涉及到钱的数量和对应面值的变化所以需要传入两个变化的量到方法中。

  再来看看递推(迭代)解法,迭代解法主要就是下一步要做的事由上一步通过某种关系来决定。比如这里种类数主要由两个变量来决定,所以需要二维数组来存储,分别是1~n,以及能够使用的哪些面值。每一个点存储的是种数。而递推解法的思路本质上是和递归一样的,只是表达的方式不一样而已。

三、代码:

public class 硬币表示 {

    public static void main(String[] args) {
        int ways;
        for (int i = 1; i < 6; i++) {
            ways = countWays(i);
            System.out.println(i + "---" + ways);
        }
        System.out.println("========================================");
        for (int i = 1; i < 6; i++) {
            ways = countWay1(i);
            System.out.println(i + "---" + ways);
        }
        System.out.println("========================================");
        for (int i = 1; i < 6; i++) {
            ways = countWay2(i);
            System.out.println(i + "---" + ways);
        }
    }
    
    /**
     * 递推解法 
     */
    public static int countWay1(int n){
        int [] coins = {1,5,10,25};
        int [][] dp = new int[4][n+1];  // 前i种面值,组合出面值j
        for(int i = 0;i<4;i++){
            dp[i][0] = 1;         // 凑出面值0,只有一种可能,第一列初始化为1
        }
        for(int j=0;j<n+1;j++){
            dp[0][j] = 1;        // 用1来凑任何面值都只有一种解法,第一行初始化为1
        }
        for (int i = 1; i < 4; i++) {
            for (int j = 1; j < n+1; j++) {
                for (int k = 0; k <= j/coins[i]; k++) {
                    dp[i][j] += dp[i-1][j-k*coins[i]];
                }
            }
        }
        return dp[3][n];
    }
    
    /**
     * 递推解法
     */
    public static int countWay2(int n){
        int[] coins = { 1, 5, 10, 25 };
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int i = 0; i < 4; i++) {
            for (int j = coins[i]; j < n+1; j++) {
                dp[j] = (dp[j]+dp[j-coins[i]]) % 1000000007;
            }
        }
        return dp[n];
    }
    
    /**
     * 递归形式
     */
    public static int countWays(int n){
        if (n<=0) {
            return 0;
        }
        return countWaysCore(n,new int[]{1,5,10,25},3);
    }

    private static int countWaysCore(int n, int[] coins, int cur) {
        if (cur==0) {
            return 1;
        }
        int res = 0;
        // 不选coins[cur]
        // 要一个
        // 要两个......
        // 循环递归  二分到多分支
        for (int i = 0; i * coins[cur] <= n; i++) {
            int shengyu = n - i * coins[cur];
            res += countWaysCore(shengyu, coins, cur - 1);
        }
        return res;
    }
}

四、结果:

猜你喜欢

转载自blog.csdn.net/OpenStack_/article/details/88730123