322. 零钱兑换

在这里插入图片描述
在这里插入图片描述
1.必须先dfs,这次的dfs+剪枝比dp还快。
先sort,把大的排前面,大的能先用就多用一点。
剪枝操作很重要,如果index超过硬币数组大小了||当前use的硬币个数+剩下需要的钱/当前硬币大小如果大于等于use,直接return,因为当前的硬币是接下来最大的,如果当前全取都优化不了的话,后面再搜索下去也是白搜索。
如果取n个当前的可以把剩下的全消耗掉,也就是left%当前==0,那么就进行更新,看res和use+当前要取几个哪个更小。取更小的。如果不能刚好取完,那就要去小的继续搜索,因为是递归,我们只需要考虑当前的硬币我们取几个,当然是取的越多越好!!从大到小遍历也算剪枝,i的初始值设为当前硬币能取的最多枚数,遍历到0个,相当于把当前从取n个到取0个全部检测一遍,这样就不会出现多取或者少取的问题了。dfs的参数也要注意,use加上的是当前取的个数,是+i;剩余减去的是i*当前硬币大小。
2.动态规划,其实也就是个背包问题,只不过更复杂,是每个物品可以取多次的问题,说到底是差不多的。dp[i][j]记录的是金额为j的情况下,你在前i枚硬币中取的硬币最少的个数可以将j取完。因为这里面存的是int而不是bool,所以我们给数组赋初始值为INF,如果dp[i][j]=inf,证明这种情况下取不到刚好的。初始化dp[i][0]=0,因为金额为0时取到的方法只有0种。
为什么是i而不是i-1,截图里的这句话很重要!!千万别搞错了。
然后双重for循环,
状态压缩的dp
在这里插入图片描述

class Solution {
public:
    const int INF = 1e9;
    //use是使用个数,left是剩下多少钱没被凑
    // void dfs(int use, vector<int>& coins, int left, int index){
    //     if(index >= coins.size() || use + left/coins[index] >= res) return;
    //     if(left % coins[index] == 0){
    //         res = min(res,use + left/coins[index]);
    //         return;
    //     }
    //     //不是大的取的越多越好,大的取的很多,最后不能刚好取到,比如22,数组是10,6,你直接取两个10肯定不行第一个数有取1个,取0个或者取多个好几种取法。比如18,你有10和6,那么10一个都不能取
    //     //所以要考虑的只是当前这一位可以取几个
    //     for(int i = left/coins[index]; i >= 0; --i){
    //         dfs(use+i,coins,left-i*coins[index],index+1);
    //     }
    // }
    int coinChange(vector<int>& coins, int amount) {
        //直接dfs,先sort,大的放前面,先用大的
        if(amount == 0) return 0;
        // sort(coins.begin(),coins.end(),greater<int>());
        // dfs(0,coins,amount,0);
        // return (res == INT_MAX?-1:res);

        //2ddp
        //取或者不取就是背包问题,先用二维dp,背包问题一定是二维dp,dp[i][j],i就是取到第i项,j就是取到的总金额,初始化的话dp[i][0]=0,取金额0永远是0种方法
        //因为这里可以一个数取多次,所以有点麻烦。如果当前nums[i]已经放不下了,就继承上一个状态。如果还放得下,就有两种情况,取或者不取,选择取和不取中较小的那个
        // vector<vector<int>> dp(coins.size()+1,vector<int>(amount+1,INF));
        // //basecase条件
        // for(int i = 0; i <= coins.size(); ++i){
        //     dp[i][0] = 0;
        // }
        // for(int i = 1; i <= coins.size(); ++i){
        //     for(int j = 1; j <= amount; ++j){
        //         //因为可以无限取,所以从小遍历到大
        //         dp[i][j] = dp[i - 1][j];
        //         if (j >= coins[i - 1]) {
        //             dp[i][j] = min(dp[i][j], dp[i][j - coins[i - 1]] + 1); 
        //         }
        //     }
        // }
        // return (dp[coins.size()][amount] == INF ? -1:dp[coins.size()][amount]);

        //状压dp
        vector<int> dp(amount+1,INF);
        dp[0] = 0;
        for(int i = 0; i < coins.size(); ++i){
            for(int j = 1; j <= amount; ++j){
                if(j >= coins[i]){
                    dp[j] = min(dp[j],dp[j-coins[i]]+1);
                }
            }
        }
        return dp[amount] == INF?-1:dp[amount];
    }
// private:
//     int res = INT_MAX;
};

完全背包,可以无限取的就是完全背包,内部从1开始遍历,因为这一层需要这一层前面的情况,所以先更新前面的。如果是01背包,就要先更新后面的,因为01背包只用到上一层的情况

class Solution {
public:
    const int INF = 1e9;
    int coinChange(vector<int>& coins, int amount) {
        //完全背包,只和上一层有关系
        //无限取就从1到amount
        vector<int> dp(amount+1,INF);
        //dp[j]表示的是用前i种硬币可以凑出j的最小个数,如果凑不出来就是INF
        dp[0] = 0;//凑0的话不管用几枚硬币都是只需要0个硬币
        for(int i = 1; i <= coins.size(); ++i){
            for(int j = 1; j <= amount; ++j){
                //看看是否可以取coins[i-1],会不会超过J
                //相当于dp[i][j]=dp[i-1][j]和dp[i][j-coins[i-1]]+1
                //为什么要从1到amount,因为dp[j-coins[i-1]]我们需要的是这一层的情况
                if(j >= coins[i-1]) dp[j] = min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount] == INF ?-1:dp[amount];
    }
};

左边的结果要更新过的,因为我们是无限取的,所以要考虑取过第i枚硬币的情况。
dp[i][j]代表前i个元素可以组成总金额j的最少硬币数,重点是可以重复取

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //完全背包问题,可以连续取当前元素
        //dp[i][j]代表前i个元素可以组成总金额j的最少硬币数,重点是可以重复取
        //状态转移方程:当取了当前元素不会<0,就有两种情况,取或者不取它dp[i][j] = min(dp[i-1][j-coins[i-1]]+1,dp[i-1][j])
        //如果会,就不取它,dp[i][j] = dp[i-1][j]
        //因为要取较小值,所以
        // vector<vector<int>> dp(coins.size()+1,vector<int>(amount+1,1e9));
        // //basecase:取总额为0的时候就是0枚硬币
        // for(int i = 0; i <= coins.size(); ++i){
        //     dp[i][0] = 0;
        // }
        // for(int i = 1; i <= coins.size(); ++i){
        //     for(int j = 1; j <= amount; ++j){
        //         if(j < coins[i-1]){
        //             dp[i][j] = dp[i-1][j];
        //         }
        //         else{
            //左边的结果要更新过的,因为我们是无限取的,所以要考虑取过第i枚硬币的情况
        //             dp[i][j] = min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
        //         }
        //     }
        // }
        // return dp[coins.size()][amount] == 1e9 ? -1 : dp[coins.size()][amount];

        //状压dp,只和左和上有关,但是左是要更新过的左,所以内部循环还是从小到大
        vector<int> dp(amount+1,1e9);
        dp[0] = 0;
        for(int i =1; i <= coins.size(); ++i){
            for(int j = 1; j <= amount; ++j){
                if(j >= coins[i-1]) dp[j] = min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount] == 1e9 ? -1 : dp[amount];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值