LintCode 740: Coin Change 2 (DP 完全背包类似题)

740. 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.

Example

Example1

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

Example2

Input: amount = 8 and coins = [2, 3, 8]
Output: 3
Explanation:
there are three ways to make up the amount:
8 = 8
8 = 3 + 3 + 2
8 = 2 + 2 + 2 + 2

Notice

You can assume below:

  • 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

Input test data (one parameter per line)How to understand a testcase?

解法1:DP,类似完全背包
dp[i][j] the # of combinations that first i group coins make up the amount j
注意:
1) dp[i][0]应该初始化为1。以input = 
8
[2,3,8]
为例
如果dp[i][0]不初始化,那么dp打印结果为

0 0 0 0 0 0 0 0 0 
0 0 1 0 1 0 1 0 1 
0 0 1 0 1 1 1 1 2 
0 0 1 0 1 1 1 1 2 

可见dp[3][8] = 2,因为k=0时,dp[3][8]+=dp[2][8]=2,而k=1时,dp[3][8]+=dp[2][0]。这里如果dp[2][0]为0,那么dp[3][8]还是2。

加上初始化后,dp打印结果为

1 0 0 0 0 0 0 0 0 
1 0 1 0 1 0 1 0 1 
1 0 1 1 1 1 2 1 2 
1 0 1 1 1 1 2 1 3 

可见dp[3][8] = 3,结果就对了。

2) dp[1][i  * coins[0]] 应该初始化为1。
3) j和k这2个循环可调换位置。

class Solution {
public:
    /**
     * @param amount: a total amount of money amount
     * @param coins: the denomination of each coin
     * @return: the number of combinations that make up the amount
     */
    int change(int amount, vector<int> &coins) {
        int n = coins.size();
        //dp[i][j] the # of combinations that first i group coins make up the amount j
        vector<vector<int>> dp(n + 1, vector<int>(amount + 1, 0));
        
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
        }
        
        for (int i = 1; i <= amount / coins[0]; ++i) {
            dp[1][i * coins[0]] = 1;
        }
        
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= amount; ++j) {
                //dp[i][j] += dp[i - 1][j]; //we do not pick any coin in group i
                for (int k = 0; k <= amount / coins[i - 1]; ++k) {
                    if (j >= k * coins[i - 1]) {
                        dp[i][j] += dp[i - 1][j - k * coins[i - 1]];
                    }
                }
            }
        }
        
        return dp[n][amount];
    }
};

上面可以简化为
 

class Solution {
public:
    /**
     * @param amount: a total amount of money amount
     * @param coins: the denomination of each coin
     * @return: the number of combinations that make up the amount
     */
    int change(int amount, vector<int> &coins) {
        int n = coins.size();
        //dp[i][j] the # of combinations that first i group coins make up the amount j
        vector<vector<int>> dp(n + 1, vector<int>(amount + 1, 0));
        
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
        }

        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= amount; ++j) {
                //dp[i][j] += dp[i - 1][j]; //we do not pick any coin in group i
                for (int k = 0; k <= amount / coins[i - 1]; ++k) {
                    if (j >= k * coins[i - 1]) {
                        dp[i][j] += dp[i - 1][j - k * coins[i - 1]];
                    }
                }
            }
        }
        
        return dp[n][amount];
    }
};

时间复杂度O(n*m*m),空间复杂度O(n*m)。m就是amount。

解法2:解法1+滚动数组优化。

注意:
1) 这里的DP是累加。不能简单的就把解法1的i变成i%2和i-1变成(i-1)%2,那样前面后面的加到一起会搞混。应该对每个i,先把 dp[i % 2][j] = dp[(i - 1) % 2][j] 先赋值,这样就不会搞混了。
2) k循环从1开始,因为k=0的情形就是dp[i % 2][j] = dp[(i - 1) % 2][j]。

class Solution {
public:
    /**
     * @param amount: a total amount of money amount
     * @param coins: the denomination of each coin
     * @return: the number of combinations that make up the amount
     */
    int change(int amount, vector<int> &coins) {
        int n = coins.size();
        //dp[i][j] the # of combinations that first i group coins make up the amount j
        vector<vector<int>> dp(2, vector<int>(amount + 1, 0));
        
        for (int i = 0; i <= 1; ++i) {   //not i <= n
            dp[i][0] = 1;
        }

        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= amount; ++j) {
                dp[i % 2][j] = dp[(i - 1) % 2][j]; //we do not pick any coin in group i
                for (int k = 1; k * coins[i - 1] <= amount; ++k) {
                    if (j >= k * coins[i - 1]) {
                        dp[i % 2][j] += dp[(i - 1) % 2][j - k * coins[i - 1]];
                    }
                }
            }
        }

        return dp[n % 2][amount];
    }
};

时间复杂度O(n*m*m),空间复杂度O(m)。

解法3:解法1+时间优化。参考九章。
根据dp[i][j] += dp[i - 1][j - k * coins[i - 1]]可得:
dp[i][j] = dp[i - 1][j - 0 * coins[i - 1]]  //当前硬币不选的方案数
            +dp[i - 1][j - 1 * coins[i - 1]]  //当前硬币选1个的方案数
            +dp[i - 1][j - 2 * coins[i - 1]]  //当前硬币选2个的方案数
            +...
            +dp[i - 1][j - k * coins[i - 1]]  
//当前硬币选k个的方案数
可以看出上面的黑体部分可以理解为当前硬币至少选1个的方案数。
而根据上面的公式我们又有
dp[i][j - coins[i - 1]] = dp[i - 1][j - coins[i - 1] - 0 * coins[i - 1]]
                            += dp[i - 1][j - coins[i - 1] - 1 * coins[i - 1]]
                            ... 
                            += dp[i - 1][j - coins[i - 1] -  (k - 1) * coins[i - 1]]
而这就是上面的黑色部分,即前i个硬币,当前硬币至少选1个的方案数。所以我们有
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]]
即前i个硬币凑出总额j的方案数就是前i-1个硬币凑出总额j的方案数 加上 前i个硬币里面当前硬币至少选1个的方案数。
代码如下:

class Solution {
public:
    /**
     * @param amount: a total amount of money amount
     * @param coins: the denomination of each coin
     * @return: the number of combinations that make up the amount
     */
    int change(int amount, vector<int> &coins) {
        int n = coins.size();
        //dp[i][j] the # of combinations that first i group coins make up the amount j
        vector<vector<int>> dp(n + 1, vector<int>(amount + 1, 0));
        
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
        }
        
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= amount; ++j) {
                dp[i][j] = dp[i - 1][j];
                if (j >= coins[i - 1]) dp[i][j] += dp[i][j - coins[i - 1]];
        }
        
        return dp[n][amount];
    }
};

时间复杂度O(mn),空间复杂度O(mn)。m就是amount。

解法4:解法3+滚动数组优化
 

class Solution {
public:
    /**
     * @param amount: a total amount of money amount
     * @param coins: the denomination of each coin
     * @return: the number of combinations that make up the amount
     */
    int change(int amount, vector<int> &coins) {
        int n = coins.size();
        //dp[i][j] the # of combinations that first i group coins make up the amount j
        vector<vector<int>> dp(2, vector<int>(amount + 1, 0));
        
        for (int i = 0; i <= 1; ++i) {
            dp[i][0] = 1;
        }
        
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= amount; ++j) {
                dp[i % 2][j] = dp[(i - 1) % 2][j];
                if (j >= coins[i - 1]) dp[i % 2][j] += dp[i % 2][j - coins[i - 1]];
            }
        }
        
        return dp[n % 2][amount];
    }
};

时间复杂度O(mn),空间复杂度O(m)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值