[LeetCode] 518. Coin Change 2 硬幣找零之二


 

You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin.

Note: You can assume that

  • 0 <= amount <= 5000
  • 1 <= coin <= 5000
  • the number of coins is less than 500
  • the answer is guaranteed to fit into signed 32-bit integer

 

Example 1:

Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

 

Example 2:

Input: amount = 3, coins = [2]
Output: 0
Explanation: the amount of 3 cannot be made up just with coins of 2.

 

Example 3:

Input: amount = 10, coins = [10] 
Output: 1

 

這道題是之前那道 Coin Change 的拓展,那道題問我們最少能用多少個硬幣組成給定的錢數,而這道題問的是組成給定錢數總共有多少種不同的方法。還是要使用 DP 來做,首先來考慮最簡單的情況,如果只有一個硬幣的話,那么給定錢數的組成方式就最多有1種,就看此錢數能否整除該硬幣值。當有兩個硬幣的話,組成某個錢數的方式就可能有多種,比如可能由每種硬幣單獨來組成,或者是兩種硬幣同時來組成,怎么量化呢?比如我們有兩個硬幣 [1,2],錢數為5,那么錢數的5的組成方法是可以看作兩部分組成,一種是由硬幣1單獨組成,那么僅有一種情況 (1+1+1+1+1);另一種是由1和2共同組成,說明組成方法中至少需要有一個2,所以此時先取出一個硬幣2,然后只要拼出錢數為3即可,這個3還是可以用硬幣1和2來拼,所以就相當於求由硬幣 [1,2] 組成的錢數為3的總方法。是不是不太好理解,多想想。這里需要一個二維的 dp 數組,其中 dp[i][j] 表示用前i個硬幣組成錢數為j的不同組合方法,怎么算才不會重復,也不會漏掉呢?我們采用的方法是一個硬幣一個硬幣的增加,每增加一個硬幣,都從1遍歷到 amount,對於遍歷到的當前錢數j,組成方法就是不加上當前硬幣的拼法 dp[i-1][j],還要加上,去掉當前硬幣值的錢數的組成方法,當然錢數j要大於當前硬幣值,狀態轉移方程也在上面的分析中得到了:

dp[i][j] = dp[i - 1][j] + (j >= coins[i - 1] ? dp[i][j - coins[i - 1]] : 0)

注意要初始化每行的第一個位置為0,參見代碼如下:      

 

解法一:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<vector<int>> dp(coins.size() + 1, vector<int>(amount + 1, 0));
        dp[0][0] = 1;
        for (int i = 1; i <= coins.size(); ++i) {
            dp[i][0] = 1;
            for (int j = 1; j <= amount; ++j) {
                dp[i][j] = dp[i - 1][j] + (j >= coins[i - 1] ? dp[i][j - coins[i - 1]] : 0);
            }
        }
        return dp[coins.size()][amount];
    }
};

 

我們可以對空間進行優化,由於 dp[i][j] 僅僅依賴於 dp[i - 1][j] 和 dp[i][j - coins[i - 1]] 這兩項,就可以使用一個一維dp數組來代替,此時的 dp[i] 表示組成錢數i的不同方法。其實最開始的時候,博主就想着用一維的 dp 數組來寫,但是博主開始想的方法是把里面兩個 for 循環調換了一個位置,結果計算的種類數要大於正確答案,所以一定要注意 for 循環的順序不能搞反,參見代碼如下:

 

解法二:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= amount; ++i) {
                dp[i] += dp[i - coin];
            }
        }
        return dp[amount];
    }
};

 

在 CareerCup 中,有一道極其相似的題 9.8 Represent N Cents 美分的組成,書里面用的是那種遞歸的方法,博主想將其解法直接搬到這道題里,但是失敗了,博主發現使用那種的遞歸的解法必須要有值為1的硬幣存在,這點無法在這道題里滿足。你以為這樣博主就沒有辦法了嗎?當然有,博主加了判斷,當用到最后一個硬幣時,判斷當前還剩的錢數是否能整除這個硬幣,不能的話就返回0,否則返回1。還有就是用二維數組的 memo 會 TLE,所以博主換成了 map,就可以通過啦~

 

解法三:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        if (amount == 0) return 1;
        if (coins.empty()) return 0;
        map<pair<int, int>, int> memo;
        return helper(amount, coins, 0, memo);
    }
    int helper(int amount, vector<int>& coins, int idx, map<pair<int, int>, int>& memo) {
        if (amount == 0) return 1;
        else if (idx >= coins.size()) return 0;
        else if (idx == coins.size() - 1) return amount % coins[idx] == 0;
        if (memo.count({amount, idx})) return memo[{amount, idx}];
        int val = coins[idx], res = 0;
        for (int i = 0; i * val <= amount; ++i) {
            int rem = amount - i * val;
            res += helper(rem, coins, idx + 1, memo);
        }
        return memo[{amount, idx}] = res;
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/518

 

類似題目:

Coin Change

9.8 Represent N Cents 美分的組成

 

參考資料:

https://leetcode.com/problems/coin-change-2/

https://leetcode.com/problems/coin-change-2/discuss/141076/Logical-Thinking-with-Clear-Java-Code

https://leetcode.com/problems/coin-change-2/discuss/99212/Knapsack-problem-Java-solution-with-thinking-process-O(nm)-Time-and-O(m)-Space

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM