Leecode 322零钱兑换
题目大意
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。
题解思路
状态确定:dp[i]表示凑成面值i所需要的最小硬币数。从0开始遍历一直到amount,求出dp[amount]即为所求解。
状态方程:
dp[i]=min(dp[i-coins[j],dp[i]),i>=coins[i]
技巧:初始时给所有的dp[i]赋值为amount+1,最多硬币数是小于这个值的,如果遍历到amount,其值dp[amount]仍为amount+1,说明没有任何一种硬币组合数使其面值等于amount,则返回-1;在这个初始赋值为最大值时,所有
边界确定:dp[0]=0;
参考代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int coinChange(int* coins, int coinsSize, int amount){
int dp[amount+1];
for(int i=0;i<=amount;i++){
dp[i]=amount+1;//便于后序检测是否有面值无法用该硬币组合数构成
}
dp[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coinsSize;j++){
if(i>=coins[j])//如果当前待求面值大于或等于该硬币的值,即一定不能选该枚硬币,否则可选
dp[i]=min(dp[i],dp[i-coins[j]]+1);
}
}
if(dp[amount]>amount)
return -1;
else return dp[amount];
}
int main(){
int n;
int coins[12],amount;
cin>>n;
for(int i=0;i<n;i++){
cin>>coins[i];
}
cin>>amount;
cout<<coinChange(coins,n,amount);
return 0;
}
题目复盘
刚开始看到这道题目时就模仿着之前做的题目往二维套,写了下面这个思虑不周的错误代码
// int dp[13][10001]={0};
// for(int j=1;j<=amount;j++)
// dp[0][j]=-1;
// dp[1][1]=1;
// for(int i=1;i<=coinsSize;i++){
// for(int j=0;j<=amount;j++)
// if(j<coins[i-1]&&dp[i-1][j]!=-1)
// dp[i][j]=dp[i-1][j];
// else if(dp[i-1][j]==-1)
// dp[i][j]=-1;
// else if(coins[i-1]<j)
// dp[i][j]=min(dp[i-1][j],dp[i-1][j-coins[i-1]]+1);
// }
// return dp[coinsSize+1][amount];
该代码错误如下:
向之前那样一次遍历前i个硬币,这种默认硬币是只能使用一次,实际每个硬币可使用多次
之后看了题解用一位数组存储状态,因为初值赋的不合理,使后面思路凌乱,又写了以下错误代码:
//一维动态规划
// int dp[10000]={0};
// //for(int i=0;i<coinsSize;i++) dp[coins[i]]=1;
// for(int i=1;i<=amount;i++)
// {
//
// int mi=10000;
// for(int j=0;j<coinsSize;j++)
// {
// if(i==coins[j]){
// dp[i]=1;
// break;
// }
// if(i>coins[j])
// dp[i]=fmin(dp[i-coins[j]]+1,dp[i]);
else if(i<coins[j])
dp[i]=fmin(dp[i],dp[i]);
// }
//
// if(mi=-1)
// dp[i]=-1;
// else
// dp[i]=mi+1;
// }
// return dp[amount];
希望下次做题时逻辑能更清晰一些,想的更明白一些,灵活运用动态规划这个思想!
Leecode 518零钱兑换II
题意
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。
解题思路
为了避免金币种类因顺序不同遍历导致的重复,此解题在外层先遍历金币的种类数,在内层遍历构成的金额数。这样构成每一金额的硬币都是按顺序排好的即可避免重复。用dp[i]存放使用前面几种类硬币构成的金额i的方案个数,在遍历到本层时再用dp[i]=dp[i]+dp[i-coins[j]]更新到达本层时构成金额i的种类数。
状态表示:dp[i]凑成金额i的组合数
状态转换: dp[i]=dp[i]+dp[i-coins[j]],i>=coins[j]
源码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int coinChange(int* coins, int coinsSize, int amount){
//int dp[amount+1]={0};
for(int i=0;i<=amount;i++)
dp[i]=0;
// for(int i=0;i<=amount;i++)
// dp[i]=amount+1; //给dp[i] 赋初值为一个不可能达到的值,如果最后真的等于这个值说明dp[i]不可能实现
dp[0]=1;
// for(int i=1;i<=amount;i++){
// for(int j=0;j<coinsSize;j++)//遍历可用的每一个硬币,看是否能使用
// if(i>=coins[j])
// dp[i]+=dp[i-coins[j]];
// }
for(int i=0;i<coinsSize;i++){//遍历可用的每一个硬币,看是否能使用
for(int j=0;j<=amount;j++)
if(j>=coins[i])
dp[j]+=dp[j-coins[i]];
}
return dp[amount];
}
int main(){
int n;
int coins[12],amount;
cin>>n;
for(int i=0;i<n;i++){
cin>>coins[i];
}
cin>>amount;
cout<<coinChange(coins,n,amount);
return 0;
}
复盘
刚开始认为这道题与322除了dp[i]含义不同与状态方程不同没什么区别,直到手动运行上述注释的错误代码后发现那种写法有重复的方案。如coins[1 2 5],amount=5;在刚开始写的时候方案:“1 2 2”和方案“ 2 2 1”是都被记入到dp[i]中的,导致了很多重复。真佩服那种第一次就能想到避免重复的方法的人!