动态规划之零钱兑换

零钱兑换I

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

示例 2:
输入: coins = [2], amount = 3
输出: -1

说明:
你可以认为每种硬币的数量是无限的。

带备忘录的递归解法

设dp[n]表示凑成金额j所需要的最少硬币个数
1.base case:

dp[负数]=-1;
dp[0]=0;

2.状态转移
在这里插入图片描述

class Solution {
public:
vector<int> memo;
int coinChange(vector<int>& coins,int amount){
    if(amount<0) return -1;
    if(0==amount) return 0;
    //1~amount对应存到memo[0]~[amount-1]
    memo.resize(amount);
    return dp(coins,amount);
}

int dp(vector<int>& coins,int amount){
    if(amount<0) return -1;
    if(0==amount) return 0;
    if(memo[amount-1]!=0)    return memo[amount-1];

    int res=INT_MAX;
    for(int i=0;i<coins.size();i++){
            int pre = dp(coins,amount-coins[i]);
            // if(pre!=-1)
            //     res=min(res,pre+1);
            if(pre!=-1&&pre<res)//避免每次调用min(res,pre+1);效率高了不少
                res=pre+1;
    }
    //循环结束后,若res还是INT_MAX,表明无法凑出金额amount
   memo[amount-1]= (res==INT_MAX?-1:res);
   return memo[amount-1];
}
};

动态规划解法

设dp[n]表示凑成金额j所需要的最少硬币个数
1.base case:

dp[0]=0;

2.状态转移
在这里插入图片描述

int coinChange(vector<int>& coins,int amount){
    if(amount<0) return -1;
    vector<int> dp(amount+1,0);
    for(int i=1;i<=amount;i++){
        int res=INT_MAX;
        for(int j=0;j<coins.size();j++){
            if(i>=coins[j]){
                int pre=dp[i-coins[j]];
                //只有pre!=-1,pre+1才能参与全局最小的比较
                if(pre!=-1)
                    res=min(res,pre+1);
            }      
        }
        //res==INT_MAX说明无法凑出i金额
        dp[i]= res==INT_MAX?-1:res;
    }
    return dp[amount];
}

模拟完全背包问题的解法

这里选择了两个状态,

dp[i][j]:表示前i类硬币,凑成金额j元,最少需要的硬币个数。

状态转移

存在:里面存放的是最少硬币个数;
不存在:里面存放的是-1

(1)dp[i-1][j]存在 dp[i][j-coins[i-1]]存在
dp[i][j]= min{dp[i-1][j],dp[i][j-coins[i-1]]+1}  

(2)dp[i-1][j]存在 dp[i][j-coins[i-1]]不存在
dp[i][j]= dp[i-1][j]     

(3)dp[i-1][j]不存在 dp[i][j-coins[i-1]]存在                      
dp[i][j]= dp[i][j-coins[i-1]]+1      

(4)dp[i-1][j]不存在 dp[i][j-coins[i-1]]不存在          
dp[i][j]= -1                                   

base case:

dp[0][1]~dp[0][amount] 都等于-1 

dp[][0]=0;
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int N=coins.size();
        
        vector<vector<int>> dp(N+1,vector<int>(amount+1,0));
        for(int i=1;i<=amount;i++){
            dp[0][i]=-1;
        }

        for(int i=1;i<=N;i++){
            for(int j=1;j<=amount;j++){
                //无法放进背包
                if(j<coins[i-1])
                //注意这里若第i类硬币无法放进背包时,需要继续看第i类前几类是否能凑成金额j
                //不能直接dp[i][j]=-1;
                    dp[i][j]=dp[i-1][j];
                    //dp[i][j]=-1;
                else{
                    if(dp[i-1][j]!=-1 && dp[i][j-coins[i-1]]!= -1)
                        dp[i][j]=min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
                    else if(dp[i-1][j]!=-1 && dp[i][j-coins[i-1]]== -1)
                        dp[i][j]=dp[i-1][j];
                    else if(dp[i-1][j]==-1 && dp[i][j-coins[i-1]]!= -1)
                        dp[i][j]=dp[i][j-coins[i-1]]+1;
                    else
                        dp[i][j]=-1;
                }
                
            }
        }

        if(dp[N][amount]==-1)
            return -1;
        return dp[N][amount];
    }
};

虽然通过了,但是效率惨不忍睹
在这里插入图片描述

零钱兑换II

518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:
在这里插入图片描述
示例 2:
在这里插入图片描述
注意:
你可以假设:
0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

直接按照完全背包求解

dp[i][j]:表示一共i类硬币,凑成总金额j的组合数

状态转移:选第i类硬币的组合数+不选第i类硬币的组合数

dp[i][j]= dp[i][j-coins[i-1]] + dp[i-1][j];  当j>=coins[i-1]时
dp[i][j]= dp[i-1][j]; 当j<coins[i-1]

base case

dp[0][...]=0
dp[...][0]=1

完整代码

int change(int amount, vector<int>& coins) {
        if(amount==0) return 1;
        int N=coins.size();
        if(N==0) return 0;

        vector<vector<int>> dp(N+1,vector<int>(amount+1));
        //base case
        for(int i=0;i<=N;i++)
            dp[i][0]=1;
        //状态转移
        for(int i=1;i<N+1;i++){
            for(int j=1;j<amount+1;j++){
                if(j>=coins[i-1])
                    dp[i][j]=dp[i][j-coins[i-1]]+dp[i-1][j];
                else
                    dp[i][j]=dp[i-1][j];
            }
        }
        return dp[N][amount];
    }

官方提供的解法

https://leetcode-cn.com/problems/coin-change-2/solution/ling-qian-dui-huan-ii-by-leetcode/

举一个例子:amount = 11,可用面值有 2 美分,5 美分和 10 美分。 请注意,数量是无限的

在这里插入图片描述
其中第二行表示硬币为0的情况
第三行表示只有面值为2美分的情况
第四行表示只有面值为2美分和5美分的情况
第五行表示有面值为2美分和5美分和10美分的情况
当前行是在上一行基础上转移而来,转移:dp[x]+=dp[x-coin]

C++代码示例:
dp[i]表示第1~k枚硬币凑成i金额的组合数

状态转移

dp[i]=dp[i]+dp[i-coin];当i>=coin时
dp[i]=dp[i];当i<coin

base case:

dp[i]={1,0,0,...,0}
int change(int amount, vector<int>& coins) {
        if(amount==0) return 1;
        int N=coins.size();
        if(N==0) return 0;
        
        vector<int> dp(amount+1);
        dp[0]=1;
        //注意下面内外循环不能交换顺序
        for(auto& coin:coins){
            for(int i=0;i<=amount;i++){
                if(i>=coin)
                    dp[i]+=dp[i-coin];
            }
        }
        return dp[amount];
       
    }

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值