题目

题目来源:力扣(LeetCode)

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

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

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


示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

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

示例 5:

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

思路分析

对于这道题,以示例1 coins = [1, 2, 5], amount = 11 为例:

凑足总额为 11 的最少硬币数,可以考虑组合中的最后一个硬币分别是1,2,5的情况,比如

  • 最后一个硬币是1的话,最少硬币数应该为【组成10的最少硬币数】+ 1枚(1块硬币)
  • 最后一个硬币是2的话,最少硬币数应该为【组成9的最少硬币数】+ 1枚(2块硬币)
  • 最后一个硬币是5的话,最少硬币数应该为【组成6的最少硬币数】+ 1枚(5块硬币)

那么凑足总额为 11 的最少硬币数为:dp[11] = min (dp[10] + 1, dp[9] + 1, dp[6] + 1)

把上面的分析转换成状态:

1、状态定义

dp[j]:凑足总额为 j 所需的最少硬币数为 dp[j]

2、确定递推公式

要得到 dp[j] (包含了面值 coins[i]),只有一个来源,那就是 dp[j - coins[i]] (没有包含 coins[i] 这个面值);

凑足总额为 j - coins[i] 的最少硬币数为 dp[j -coins[i]],那么只需要再加上一个面值为 coins[i] 的硬币,即 dp[j - coins[i]] + 1 即可得到 dp[j],即 dp[j] = dp[j - coins[i]] + 1

dp[j] 要取所有 dp[j - coins[i]] + 1 中最少的,因此可得递推公式为:
**dp[j] = min(dp[j - coins[i]] + 1, dp[j])**

3、状态初始化

首先凑足总金额为 0 所需硬币的个数一定是 0,那么 dp[0] = 0;

那么其他下标对应的数值呢?

考虑到递推公式的特性,dp[j] 必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

所以下标非0的元素都应该是最大值。

dp 初始化代码:

  1. let dp = new Array(amount + 1).fill(Number.MAX_VALUE);
  2. dp[0] = 0;

4、确定遍历顺序

本题求硬币最小个数,那么硬币有顺序和没有顺序都可以,都不影响硬币的最小个数。

所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

那么我采用coins放在外循环,target在内循环的方式。

遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。

代码实现

  1. /**
  2. * @param {number[]} coins
  3. * @param {number} amount
  4. * @return {number}
  5. */
  6. var coinChange = function (coins, amount) {
  7. // dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖
  8. let dp = new Array(amount + 1).fill(Number.MAX_VALUE);
  9. // 凑足总金额为0所需钱币的个数一定是0,初始化 dp[0] = 0
  10. dp[0] = 0;
  11. for (let i = 0; i < coins.length; i++) { // 遍历物品
  12. for (let j = coins[i]; j <= amount; j++) { // 遍历背包
  13. if (dp[j - coins[i]] != Number.MAX_VALUE) { // 如果dp[j - coins[i]]是初始值则跳过
  14. dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j]);
  15. }
  16. }
  17. }
  18. if (dp[amount] == Number.MAX_VALUE) return -1;
  19. return dp[amount];
  20. };