题目
链接:https://leetcode-cn.com/problems/coin-change
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
动态规化
此题相当于完全背包问题,与01背包问题的不同在于,完全背包问题中的物品可以无限取,而01背包问题每个问题只能取一次或者不取。通过零一背包的状态转移方程可以推导出完全背包的状态转移方程:
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
,
j
−
v
[
i
]
)
+
w
[
i
]
)
f(i,j) = max(f(i-1,j), f(i, j - v[i]) +w[i])
f(i,j)=max(f(i−1,j),f(i,j−v[i])+w[i])
背包问题需要注意题目问的是恰好放满背包还是,背包可以不放满,如果是恰好则初始化条件只能初始化 f ( 0... N , 0 ) 为 0 , f ( 0 , 1... V ) 为 无 解 ( + i n f i n i t y 或 者 − I n f i n i t y 根 据 题 目 求 最 大 还 是 最 小 初 始 化 ) f(0...N,0)为0, f(0, 1...V)为无解(+infinity 或者-Infinity根据题目求最大还是最小初始化) f(0...N,0)为0,f(0,1...V)为无解(+infinity或者−Infinity根据题目求最大还是最小初始化);如果不要求背包被刚好放满则初始化条件为 f ( 0... N , 0 ) = 0 , f ( 0 , 1... V ) = 0 f(0...N, 0) = 0,f(0, 1...V) = 0 f(0...N,0)=0,f(0,1...V)=0
此题属于背包恰好放满的情况,下面是二维dp的代码
class Solution {
public int coinChange(int[] coins, int amount) {
// 物品的数量, 背包的大小为amount,
//此题要求物品能恰好放满背包,所有只有dp[0][0] 为 1
int N = coins.length;
int[][] dp = new int[N + 1][amount + 1];
Arrays.fill(dp[0],Integer.MAX_VALUE - 1);
dp[0][0] = 0;
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] = Math.min(dp[i][j], dp[i][j - coins[i-1]] + 1);
}
}
}
return dp[N][amount] == Integer.MAX_VALUE - 1 ? -1 : dp[N][amount];
}
}
可以对上面的二维dp进行空间优化改为1维dp,改进后可以发现完全背包只需要将 0-1 背包的逆序遍历 dp 数组改为正序遍历即可。
class Solution {
public int coinChange(int[] coins, int amount) {
// 物品的数量, 背包的大小为amount,
//此题要求物品能恰好放满背包,所有只有dp[0][0] 为 1
int N = coins.length;
int[] dp = new int[amount + 1];
Arrays.fill(dp,Integer.MAX_VALUE - 1);
dp[0] = 0;
for (int i = 1; i <= N; i++) {
for (int j = coins[i-1]; j <= amount; j++) {
dp[j] = Math.min(dp[j], dp[j - coins[i-1]] + 1);
}
}
return dp[amount] == Integer.MAX_VALUE - 1 ? -1 : dp[amount];
}
}