零钱兑换是典型的dp,一共有两种题型,第一种是求固定金额需要的最少硬币数,如:322. 零钱兑换。第二种是求固定金额,硬币所有可能的组合数。如518. 零钱兑换 II和面试题 08.11. 硬币
322. 零钱兑换
解法一:备忘录从后往前计算dp
F(S):组成金额 SS 所需的最少硬币数量
[Co …Cn−1] :可选的 n 枚硬币面额值
递归方程:F(S)=F(S−C)+1
但我们不知道最后一枚硬币的面值是多少,我们需要枚举每个硬币面额值并选择其中的最小值
class Solution {
private int coinChange(int []coins,int amount,int []res)//res状态方程数组,存放当前需要多少个硬币
{
if(amount<0)//如果数量小于0,说明此硬币不合适
return -1;
if(amount==0)//数量为0,恰好可以分光
return 0;
if(res[amount-1]!=0)return res[amount-1];//如果不为0,是状态方程的内容
int min=Integer.MAX_VALUE;
for(int coin:coins)
{
int temp=coinChange(coins,amount-coin,res);//递归方程F(S)=F(S−C)+1,判断coin是不是可以变成那个1
if(temp<min&&temp>=0)
min=temp+1;//coin可以分配,可以分1
}
min=(min==Integer.MAX_VALUE?-1:min);
res[amount-1]=min;//当前函数的参数是amount,此处更新res[amount-1]
return res[amount-1];
}
public int coinChange(int[] coins, int amount) {
if(amount==0)
return 0;
return coinChange(coins,amount,new int[amount]);
}
}
解法2:从前往后dp
dp[j]数组的含义是:j金额最少使用多少个硬币
递归方程dp[j]=Math.min(dp[j],dp[j-coin]+1);
class Solution {
public int coinChange(int[] coins, int amount) {
int max=amount+1;
int[]dp=new int[amount+1];
Arrays.fill(dp,max);
dp[0]=0;
for(int j=1;j<=amount;j++){
for(int coin:coins){
if(coin<=j)
dp[j]=Math.min(dp[j],dp[j-coin]+1);
}
}
return dp[amount]>amount?-1:dp[amount];
}
}
518. 零钱兑换 II
dp[j]数组的含义是:j金额最少使用多少种硬币组合方案
递归方程dp[i]=dp[i-coin]+dp[i];
遍历硬币面值,当前组合数+使用该硬币组合数
class Solution {
public int change(int amount, int[] coins) {
int[]dp=new int[amount+1];
dp[0]=1;
for(int coin:coins){
for(int i=coin;i<=amount;i++){
dp[i]=dp[i-coin]+dp[i];
}
}
return dp[amount];
}
}
面试题 08.11. 硬币
class Solution {
public int waysToChange(int n) {
int[]coins=new int[]{1,5,10,25};
int[]dp=new int[n+1];//取值取到n
dp[0]=1;
for(int i=0;i<4;i++){//分四个硬币
for(int j=coins[i];j<=n;j++){
dp[j]=dp[j-coins[i]]+dp[j];//加上分四种硬币的分法
dp[j]=dp[j]%1000000007;
}
}
return dp[n];
}
}