第七章 遞迴、DFS、剪枝、回溯等問題 ------------- 7.4 硬幣表示某個給定數值

Curtis_發表於2019-03-22

一、題目:

  假設我們有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;
    }
}

四、結果:

 

相關文章