题目描述
给你一个整数数组 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.length <= 12 1 <= coins[i] <= 231 - 1 0 <= amount <= 104
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
🎒完全背包问题
完全背包🎒
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
动态规划五部曲:
-
确定dp数组及其下标含义:
- dp[j]:总金额为j的最少硬币数;
-
确定递推公式:
- 逆推:dp[j]一定是由dp[j-coins[i]]逆推而来;
- 总金额为j-coins[i]的最少硬币数为dp[j-coins[i]], 此时只需要加一个硬币coins[i],就是dp[j]
- dp[j]取dp[j-coins[i]]+1与上次赋值dp[j]之间的最小值
- 递推公式:dp[j]=Math.min(dp[j-coins[i]]+1,dp[j])
-
dp数组初始化
- 首先amount===0的最少硬币数一定是0:dp[0]=0;
- 考虑到递推公式是求最小值,Math.min(dp[j-coins[i]]+1,dp[j])
- 我们将dp[j]初始化为最大值Infinity,以便新值替换掉初始值;
动画演示
举例:如图演示递推dp数组:
我们初始化coins=[6,4,3,1,5];当i=2,coins[2]=3时,假设求dp[4]时,即总额amount=4时的最新少银币:
根据递推公式dp[j]=Math.min(dp[j-coins[i]]+1,dp[j])
此时dp[j-coins[i]]=dp[4-3]=dp[1]=Infinity
因此dp[j]=Math.min(Infinity+1,1)=1;即dp[4]=1;
时空复杂度
- 时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn) 的时间复杂度。
- 空间复杂度:O(S)。数组dp 需要开长度为总金额 S 的空间。
代码
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
// 完全背包问题
let n=coins.length;
// 1、初始化dp,amount==0时,dp[amount]=0
let dp=new Array(amount+1).fill(Infinity)
dp[0]=0;
// 先遍历物品,再遍历背包
for(let i=0;i<n;i++){ // 遍历n种物品
for(let j=coins[i];j<=amount;j++){
dp[j]=Math.min(dp[j-coins[i]]+1,dp[j])
}
}// end of for
return dp[amount]===Infinity?-1:dp[amount]
};