注意几件事情:

  • dp 数组的每一个元素的值有上限,因为硬币的数值都是正整数,因此需要的硬币数量最多不会超过 amount + 1
  • 从可以转移的地方开始计算即可;
  • 和完全背包问题产生联系。

方法一:动态规划

使用最基本的「动态规划」的思路。

第 1 步:状态定义

dp[i] 表示凑成金额为 i 的最少硬币数。

第 2 步:状态转移方程

根据题目示例 1:coins = [1, 2, 5]amount = 11。很容易想到:

总金额 11 ,根据可以选择的硬币列表 [1, 2, 5] 可以这样拆解:

  • 11 = 1 + (11 - 1):表示 11 可以拆成面值为 1 的 1 个硬币和凑成面值为 10 的最小硬币数之和;
  • 11 = 2 + (11 - 2):表示 11 可以拆成面值为 2 的 1 个硬币和凑成面值为 9 的最小硬币数之和;
  • 11 = 5 + (11 - 5):表示 11 可以拆成面值为 5 的 1 个硬币和凑成面值为 6 的最小硬币数之和;

三者取最小值。下面写出状态转移方程:

d p [ i ] = min ⁡ { 1 + d p [ i − c o i n s [ j ] ] } dp[i] = \min \{1 + dp[i - coins[j]]\} dp[i]=min{1+dp[icoins[j]]}

这里 j 是硬币列表的序号。注意 i - coins[j] 必须大于等于 0 ,才有意义。

此外,需注意:有两个非常特殊的状态:

  • dp[0]0 这个面值是一个在填表的时候被参考的数值,如果总面值为 5,刚好有个硬币的面值也为 5 ,此时 dp[5] = 1 + dp[5 - 5] = 1,故设置 dp[0] = 1
  • 也有可能出现例 2 的情况,此时需要设置 dp[i] = - 1,这道题问的又是最小值,因此,初始化的时候不能设置为 0 ,应该设置为一个足够大的数。

第 3 步:初始化

(上面已经讨论过了)

第 4 步:输出

输出 dp[amount] 即可。

第 5 步:是否可以空间优化

每一步都有可能用到之前的状态,因此不能压缩。

再次提醒自己:初始化的时候,因为要比较的是最小值,因此不能初始化为 − 1 -1 1。并且这里 − 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] != -1) {
                    dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
                }
            }

            if (dp[i] == amount + 1) {
                dp[i] = -1;
            }
        }

        if (dp[amount] == amount + 1) {
            dp[amount] = -1;
        }
        return dp[amount];
    }
}

可以用“完全背包”的模板。(代码是正确的,原因我还没有想得特别清楚。)

import java.util.Arrays;

public class Solution {

    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }

        if (dp[amount] == 9999) {
            dp[amount] = -1;
        }
        return dp[amount];
    }
}

注意:1、初始化的时候,赋值成一个很大的数就可以了,int 的最大值就有可能造成越界;

2、不要在代码中做打印输出,会拖慢时间。