您现在的位置是:首页 >技术交流 >leetcode:322. 零钱兑换(暴力dfs,记忆化dfs,动态规划(朴素+优化),bfs+贪心)网站首页技术交流

leetcode:322. 零钱兑换(暴力dfs,记忆化dfs,动态规划(朴素+优化),bfs+贪心)

哆啦刘小洋 2024-06-17 11:27:54
简介leetcode:322. 零钱兑换(暴力dfs,记忆化dfs,动态规划(朴素+优化),bfs+贪心)

记录常规的完全背包问题模型


在这里插入图片描述

1.由于每件物品可以无限取,那么可以发现这是一个完全背包问题模型。

1.暴力dfs

最后要求的是:n种硬币,凑成总金额为amount。每种硬币无限取,最少需要多少枚硬币?
那么根据问题来写递归:

class Solution 
{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        int n=coins.size(); 
        //dfs(i)表示剩余价值为i使用硬币的最少数量
        function<int(int)>dfs=[&](int amount)->int  
        {
            if(amount<0)return -1;
            if(amount==0)return 0;
            int Min=0x3f3f3f3f;
            for(int i=0;i<n;i++)
            {
                int t=dfs(amount-coins[i]);
                if(t>=0&&Min>t)Min=t+1;
            }
            return Min;
        };
        int ans=dfs(amount);
        
        return ans==0x3f3f3f3f?-1:ans;
    }
};

2.优化dfs,记忆化dfs

因为递归存在大量重复,所以使用记忆化优化

class Solution 
{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        int n=coins.size();
        int memo[10010];
        memset(memo,0,sizeof memo);
        function<int(int)>dfs=[&](int amount)->int  //剩余价值为amount时最少需要的硬币数量
        { 
            if(amount<0)return -1;
            if(amount==0) return 0;
            if(memo[amount]!=0)return memo[amount];
            int Min=0x3f3f3f3f;
            for(int i=0;i<n;i++)
            {
                int t=dfs(amount-coins[i]);
                if(t>=0&&t<Min)Min=t+1;
            }
            memo[amount]=Min;
            return Min;
        };
        int ans=dfs(amount);
        return ans==0x3f3f3f3f?-1:ans;

    }
};

3.动态规划

动态规划是这道题最常规的做法。那么直接拿出模板

class Solution 

{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        
        int n=coins.size();
        int dp[13][10010];   //dp[i][j]表示前i种硬币,组成价值为amount的最少硬币数量
        memset(dp,0x3f,sizeof dp);
        for(int i=0;i<=n;i++)
            dp[i][0]=0;
        for(int i=1;i<=n;i++) //枚举物品
        {
            int v=coins[i-1];
            for(int j=0;j<=amount;j++)  //枚举体积
            {
                for(int k=0;k*v<=j;k++)  //枚举当前物品的数量
                {
                    dp[i][j]=min(dp[i][j],dp[i-1][j-k*v]+k);
                }
            }
        }
        return dp[n][amount]==0x3f3f3f3f?-1:dp[n][amount];
    }
};

优化很简单,看下面:
在这里插入图片描述

class Solution 

{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        
        int n=coins.size();
        int dp[13][10010];   //dp[i][j]表示前i种硬币,组成价值为amount的最少硬币数量
        memset(dp,0x3f,sizeof dp);
        for(int i=0;i<=n;i++)
            dp[i][0]=0;
        for(int i=1;i<=n;i++) //枚举物品
        {
            for(int j=0;j<=amount;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(j-coins[i-1]>=0)
                    dp[i][j]=min(dp[i][j],dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[n][amount]==0x3f3f3f3f?-1:dp[n][amount];
    }
};

实际上到这一步已经很完善了,但是空间上可以继续优化一下,二维优化为一维

class Solution 

{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        
        int n=coins.size();
        int dp[10010];   //dp[i][j]表示前i种硬币,组成价值为amount的最少硬币数量
        memset(dp,0x3f,sizeof dp);
        dp[0]=0;   //dp[i][0]
        for(int i=1;i<=n;i++) //枚举物品
        {
            for(int j=coins[i-1];j<=amount;j++)
            {
                //dp[i][j]=dp[i-1][j];
                dp[j]=min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount]==0x3f3f3f3f?-1:dp[amount];
    }
};

这里一维版本为什么第二个for循环不需要逆序呢?看公式:
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ j − c o i n s [ i − 1 ] ] + 1 ) ; dp[i][j]=min(dp[i][j],dp[i][j-coins[i-1]]+1); dp[i][j]=min(dp[i][j],dp[i][jcoins[i1]]+1);没有数据污染,更新的dp不会造成影响后续更新。

4.bfs

由于是求最少,那么可以抽象成最短路问题。先对硬币排序,然后一步步向四周扩散。3

class Solution 
{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        sort(coins.begin(), coins.end(), greater<int>());// 剪枝,首先减去大数再减去小数
        
        queue<int> q;
        q.push(amount);
        unordered_set<int> vis;
        vis.insert(amount);

        int ans = 0;
        while (!q.empty()) {
            int size = q.size();
            while (size --) {
                int top = q.front();
                q.pop();
                if (top == 0) return ans;// 当amount已经减到0就返回广搜的层数
                for (int i = 0; i < coins.size(); i ++) {
                    int num = top - coins[i];
                    if (!vis.count(num) && num >= 0) {
                        vis.insert(num);
                        q.push(num);
                    }
                }
            }
            ans ++;
        }
        return -1;
    }
};

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。