排列总和/组合总和Ⅳ

322. 零钱兑换

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

计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 :

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

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

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 2^31 - 1
0 <= amount <= 10^4

解题思路

动态规划

此题coins里面的元素可以重复使用,故为完全背包问题

1.确定dp数组及下标的含义

dp[j] 表示凑成金额为 j 使用硬币最少的数量为dp[j]

2.确定递推公式

dp[j] 可以由dp[j - coins[i]] 得到,若考虑coins[i],那么凑成金额为 j 的最少硬币数量就为dp[j - coins[i]] + 1

而dp[j] 要取所有dp[j] 中的较小者

故递推公式为,dp[j] = Math.min(dp[j], dp[j - coins[i]]+1)

3.初始化dp数组

非排列组合问题,dp[0] = 0,表示凑成总金额为0的硬币数量最少是0

对于非0下标,dp[j]必须初始化为一个最大值,否则再求最小值时会被0覆盖

只有dp[j-coins[i]] 不为最大值时,才进行比较,因为dp[j-coins[i]]为最大值表示没有方案可以凑成j - coins[i]

4.确定遍历顺序

非排列组合问题,先遍历物品后遍历背包或先遍历背包后遍历物品都可以

这里选先遍历物品,后遍历背包,完全背包问题要从小到大遍历背包

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int coinChange(int[] coins, int amount) {
//dp[j] 表示凑成金额为 j 使用硬币最少的数量为dp[j]
int[] dp = new int[amount+1];
dp[0] = 0; //表示凑成金额为0的最少硬币数为0
for(int i = 1; i < dp.length; i++){
dp[i] = Integer.MAX_VALUE; //非0下标初始化为最大值
}

for(int i = 0 ; i < coins.length; i++){//遍历物品
for(int j = coins[i]; j<=amount; j++){ //遍历背包
if(dp[j-coins[i]] != Integer.MAX_VALUE){ //若dp[j-coins[i]]不为初始值才进行比较
dp[j] = Math.min(dp[j], dp[j-coins[i]]+1);
}
}
}
if(dp[amount] == Integer.MAX_VALUE) return -1;
return dp[amount];

}
}
  • 时间复杂度:$O(n\times m)$,n为coins.length,m为amount
  • 空间复杂度:$O(m)$

排列总和/组合总和Ⅳ
http://example.com/2023/01/24/算法/动态规划/17. 零钱兑换/
作者
PALE13
发布于
2023年1月24日
许可协议