题目描述

题目链接:518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例1:

1
2
3
4
5
6
7
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例2:

1
2
3
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例2:

1
2
输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

我的题解

方法一:01背包思想

思路

设dp[i][j]为在前i个银币中凑成金额为j的种类数量,考虑第i个硬币参不参与凑数过程:

  • 不参与,则dp[i][j] = dp[i-1][j]
  • 参与,考虑第i个硬币选k次,最多选 j / coins[i]次,因此求dp[ i - 1][j - coins[i] * k]之和

考虑动态规划初始化,因为当硬币值为n时,可以凑成金额为k*n的种类有一种,因此为了保证dp[ i - 1][j - coins[i] * k]中j - coins[i] * k == 0时有一种选择,因此我们把dp[i][0]初始化为1.

以示例一举例,求得dp数组为

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int change(int amount, int[] coins) {
int[][] dp = new int[coins.length + 1][amount + 1];
for (int i = 0; i < dp.length; i++) {
dp[i][0] = 1;
}
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
for (int k = 0; k <= j / coins[i - 1]; k++) {
dp[i][j] += dp[i - 1][j - k * coins[i - 1]];
}
}
}
return dp[coins.length][amount];
}
}

方法二:完全背包

思路

和上述01背包思想不同,完全背包直接利用了dp[i][j]语义,方法一解法是通过遍历来获取含第i个硬币的组合,因此多了一层循环,而完全背包递推公式为:

1
dp[i][j] += dp[i - 1][j] + dp[i][j - coins[i - 1]];

注意后半部分,dp[i][j - coins[i - 1]],也就是直接添加第i个硬币已经参与并且值为j - coins[i-1]的种类

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int change(int amount, int[] coins) {
int[][] dp = new int[coins.length + 1][amount + 1];
for (int i = 0; i < dp.length; i++) {
dp[i][0] = 1;
}
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
if (j >= coins[i - 1]) {
dp[i][j] += dp[i - 1][j] + dp[i][j - coins[i - 1]];
} else {
dp[i][j] += dp[i - 1][j];
}
}
}
return dp[coins.length][amount];
}
}

使用一维数组优化:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}