[LeetCode] 322. Coin Change
You are given an integer array coins
representing coins of different denominations and an integer amount
representing a total amount of money.
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1
.
You may assume that you have an infinite number of each kind of coin.
Example 1:
Input: coins = [1,2,5], amount = 11 Output: 3 Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3 Output: -1
Example 3:
Input: coins = [1], amount = 0 Output: 0
Example 4:
Input: coins = [1], amount = 1 Output: 1
Example 5:
Input: coins = [1], amount = 2 Output: 2
Constraints:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
零钱兑换。
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题意是给一个数组 coins 表示一些零钱的面值和一个总价值 amount,问如何组合能使得 amount 使用的硬币数量最少。若没有这样的组合就输出 -1,硬币可以无限次使用。影子题983。
思路是动态规划。看了这个题目和这个题解,我算是对 DP 有点了解了,也搞清楚什么叫做自顶向下(记忆化搜索)和自底而上(动态规划)了。这个题 DP 数组的定义是要达到某一个 amount 需要用到的最少数目的硬币数量。那么很显然 dp[0] = 0,如果 amount = 0,无需任何硬币。接着看从 1 到 amount 这个范围,如果满足以下两个条件,dp[i] 就会有解了。
- 当前 amount - coins[j] >= 0,需要遍历每个不同的 coins,看每种不同的组合,只有当前 amount - coins[j] >= 0 的时候,才说明叠加某一个 coin 才有意义。一个反例是,如果当前 amount 是 5,但是 coin 遍历到 10,就无需操作,因为剩下的 amount < 0 了
- dp[i - coins[j]] < Integer.MAX_VALUE。判断这个条件是去判断这个值 dp[i - coins[j]] 是否已经被计算过
其他的看代码就应该能懂。举个例子,11 既可以 = 5 + 5 + 1,也可以 = 11 个 1 相加。这里处理的时候,因为每一个dp值都会寻求最小值,所以 5 + 5 + 1 的组合会被保留。
时间O(硬币数量 * amount)
空间O(amount)
Java实现
1 class Solution { 2 public int coinChange(int[] coins, int amount) { 3 // memo[n]的值: 表示的凑成总金额为n所需的最少的硬币个数 4 int[] memo = new int[amount + 1]; 5 memo[0] = 0; 6 for (int i = 1; i <= amount; i++) { 7 int min = Integer.MAX_VALUE; 8 for (int j = 0; j < coins.length; j++) { 9 if (i - coins[j] >= 0 && memo[i - coins[j]] < min) { 10 min = memo[i - coins[j]] + 1; 11 } 12 } 13 memo[i] = min; 14 } 15 return memo[amount] == Integer.MAX_VALUE ? -1 : memo[amount]; 16 } 17 }
JavaScript实现
1 /** 2 * @param {number[]} coins 3 * @param {number} amount 4 * @return {number} 5 */ 6 var coinChange = function(coins, amount) { 7 let memo = new Array(amount + 1); 8 memo[0] = 0; 9 for (let i = 1; i <= amount; i++) { 10 let min = Number.MAX_VALUE; 11 for (let j = 0; j < coins.length; j++) { 12 if (i - coins[j] >= 0 && memo[i - coins[j]] < min) { 13 min = memo[i - coins[j]] + 1; 14 } 15 } 16 memo[i] = min; 17 } 18 return memo[amount] == Number.MAX_VALUE ? -1 : memo[amount]; 19 };
我再提供一个自上而下的思路,需要用到备忘录。我们直接用 helper 函数去求如果要凑成最后的 amount 需要多少个硬币。对于过程中遇到的较小的 amount,如果我们能得到一个答案,我们就将他存到 memo 数组里,比如例子一,如果我知道 11 可以被 3 个硬币组合,那么如果我需要去求 22,且 22 被分解成 11 + 11 的话,我就可以用 O(1) 的时间得知 22 可以被拆分成 6 个硬币的组合而无需再从 1 开始重新开始算一遍。
时间O(硬币数量 * amount)
空间O(n) - memo
Java实现
1 class Solution { 2 int[] memo; 3 4 public int coinChange(int[] coins, int amount) { 5 if (coins.length == 0) { 6 return -1; 7 } 8 memo = new int[amount + 1]; 9 return helper(coins, amount); 10 } 11 12 // memo[n] 是备忘录,表示钱币 n 可以被换取的最少的硬币数,不能换取就为-1 13 // helper函数的目的是为了找到 amount 数量的零钱可以兑换的最少硬币数量,返回其值int 14 public int helper(int[] coins, int amount) { 15 if (amount < 0) { 16 return -1; 17 } 18 if (amount == 0) { 19 return 0; 20 } 21 // 记忆化的处理,memo[n]用赋予了值,就不用继续下面的循环 22 // 直接的返回memo[n] 的最优值 23 if (memo[amount] != 0) { 24 return memo[amount]; 25 } 26 int min = Integer.MAX_VALUE; 27 for (int coin : coins) { 28 int res = helper(coins, amount - coin); 29 if (res >= 0 && res < min) { 30 // 加1,是为了加上得到res结果的那个步骤中,兑换的一个硬币 31 min = res + 1; 32 } 33 } 34 memo[amount] = min == Integer.MAX_VALUE ? -1 : min; 35 return memo[amount]; 36 } 37 } 38 39 // 自上而下
相关题目