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.
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
You can assume below:
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 &coins) {
int n = coins.size();
//dp[i][j] the # of combinations that first i group coins make up the amount j
vector> dp(n + 1, vector(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 &coins) {
int n = coins.size();
//dp[i][j] the # of combinations that first i group coins make up the amount j
vector> dp(n + 1, vector(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 &coins) {
int n = coins.size();
//dp[i][j] the # of combinations that first i group coins make up the amount j
vector> dp(2, vector(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 &coins) {
int n = coins.size();
//dp[i][j] the # of combinations that first i group coins make up the amount j
vector> dp(n + 1, vector(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 &coins) {
int n = coins.size();
//dp[i][j] the # of combinations that first i group coins make up the amount j
vector> dp(2, vector(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)。