LeetCode 518. Coin Change 2(硬币找零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.

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

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

Reference Answer

思路分析
这道题是之前那道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,参见代码如下:

C++ Code

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。第一感觉是完全背包问题,但其实由于没有重量和价值的对应关系,所以不一样。

生成了一个一维数组dp,dp[i]代表了生成总价值为i有多少方案。

对已有的所有面值的硬币进行遍历,其实思路很简单:dp[i] += dp[i - coin],价值为i的解决方案应该加上价值为i - coin的解决方案。

时间复杂度是O(L * A),空间复杂度是O(A); A = amount.

参见代码如下:

C++ Version:

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];
    }
};

这道题其实就是变形版的完全背包问题

Note

  • 初始值到底是赋值为 0 还是赋值为 1 是要依据题意来定的,本题是设置为 1,因为要求的是组成硬币个数,因为 dp[i] 表示组成钱数 i 的不同方法,dp[0] = 1,表示组成钱数为 0 只有一种方法,即。

参考文献

[1] https://blog.csdn.net/fuxuemingzhu/article/details/82845212
[2] http://www.cnblogs.com/grandyang/p/7669088.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值