N刷犯得一个错误:将i>=coins[j]放在了for循环判断条件中,导致解答错误。为什么在《完全平方数》就可以呢,因为那里的平方数是有序递增的,而这里的硬币大小不是有序的。
题目
https://leetcode-cn.com/problems/coin-change/
类似题目:279 完全平方数
完全背包问题
硬币数量是无限次使用的,所以是一个完全背包问题。
完全背包,需要正序遍历背包。
初始状态:总金额为0,背包容量为0时,没有硬币能凑成结果。
由于转移时比较min,所以初始化时要设置一个最大值。如果默认初始化dp[j]=0,就无法转移状态。
我们要求的是凑成amount的最少硬币数。
转移状态:dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
凑成j-coins[i]的硬币数,加上这一枚硬币i,总价值加上coins[i]的面值就是j。
求排列时,外层遍历背包,内层遍历物品(因为物品出现的不同顺序视为不同的排列)。
求组合时,外层可以遍历物品也可以遍历背包。
求种类数时,我们将dp数组的长度设为amount+1,也就是容量从0到amount。这是因为要考虑容量为0的情况,这是不会出现的情况,但是是一种初始情况,用于后续的状态转移。
class Solution {
public int coinChange(int[] coins, int amount) {
if(amount==0) return 0;
//Arrays.sort(coins);
int[] dp=new int[amount+1];
Arrays.fill(dp,10010);
dp[0]=0;
for(int i=0;i<coins.length;i++){
for(int j=coins[i];j<=amount;j++){
dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
}
}
return dp[amount]==10010?-1:dp[amount];
}
}
方法一:自底向上的动态规划
代码
class Solution {
/*
自底向上的动态规划
*/
public int coinChange(int[] coins, int amount) {
if(coins.length==0) return -1;
int[] memo=new int[amount+1];
Arrays.fill(memo,amount+1);//初始化为一个不可能到达的数量amount+1。设置为Integer.MAX_VALUE会出错
memo[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(i>=coins[j]){
memo[i]=Math.min(memo[i],memo[i-coins[j]]+1);
//memo[i]的两种情况:包含coins[j],不包含conis[j]
}
}
}
if(memo[amount]==amount+1)//没结果
return -1;
else
return memo[amount];
}
}
复杂度
coins长度为m,amount为n
时间复杂度O(mn):对1…amount取值,需要枚举coins。
空间复杂度O(n):dp数组占用的空间
结果
没有将数组初始化为最大值
由于在更新过程中,要取最小值min,如果数组初始化默认为0,那么在比较时就都会选择0。会出现结果0的错误。
各种错误情况,进行了分别判断。
class Solution {
public int coinChange(int[] coins, int amount) {
if(amount==0) return 0;
int[] dp=new int[amount+1];
dp[0]=0;//凑0元,需要0个硬币
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){//遍历硬币
if(coins[j]<=i){//硬币面额<=总金额
//dp[i-coins[j]]//凑成i-coins[j]所需要的硬币数
if(i-coins[j]!=0&&dp[i-coins[j]]==0) continue;
//x总数不为0,但没有一种方法能组成dp[x]=0。
if(dp[i]!=0)
//dp[i]已经被上一个小的硬币更新过,尝试用当前硬币更新
dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
else
dp[i]=dp[i-coins[j]]+1;
}
}
}
return dp[amount]==0?-1:dp[amount];
}
}
/**
给定容量amount,求最小物品数,物品可以重复使用,所以是完全背包。
*/
方法二 递归(会超时)
class Solution {
int res=Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
find(coins,amount,0);
return res==Integer.MAX_VALUE?-1:res;
}
public void find(int[] coins,int amount,int count){
if(amount<0){
return;
}
if(amount==0){
res=Math.min(res,count);
}
for(int coin:coins){
find(coins,amount-coin,count+1);
}
}
}