322. 零钱兑换
难度中等
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
方法一:动态规划
思路:
- 看题目的问法,只问最优值是多少,没有要我们求最优解,一般情况下就是「动态规划」可以解决的问题;
- 最优子结构其实比较明显,根据示例 1:
输入: coins = [1, 2, 5], amount = 11
凑成面值为 11
的最小***数可以由以下 33 者的最小值得到:
1、凑成面值为 10
的最小***数 + 面值为 1
的这一枚***;
2、凑成面值为 9
的最小***数 + 面值为 2
的这一枚***;
3、凑成面值为 6
的最小***数 + 面值为 5
的这一枚***;
即 dp[11] = min (dp[10] + 1, dp[9] + 1, dp[6] + 1)
。
- 可以直接把题目的问法设计成状态。
第 1 步:定义「状态」
dp[i]
:凑齐总价值 i
需要的最少***数,状态就是问的问题。
第 2 步:写出「状态转移方程」
根据对具体例子的分析:
dp[amount] = min(1 + dp[amount - coin[i]]) for i in [0, len - 1] if coin[i] <= amount
注意的是:
1、首先***的面值首先要小于等于***当前要凑出来的面值;
2、剩余的那个面值应该要能够凑出来,例如:求 dp[11]
需要参考 dp[10]
,如果不能凑出来的话,dp[10]
应该等于一个不可能的值,可以设计为 11 + 1
,也可以设计为 -1
,它们的区别只是在具体的代码编写细节上不一样而已。
再强调一次:新状态的值要参考的值以前计算出来的「有效」状态值。这一点在编码的时候需要特别注意。
因此,不妨先假设凑不出来,因为比的是小,所以设置一个不可能的数。
参考代码 1:
- Java
import java.util.Arrays;
public class Solution {
public int coinChange(int[] coins, int amount) {
// 给 0 占位
int[] dp = new int[amount + 1];
// 注意:因为要比较的是最小值,这个不可能的值就得赋值成为一个最大值
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i - coin >= 0 && dp[i - coin] != amount + 1) {
dp