LeetCode刷题整理——背包问题

大佬的动态规划题解
LeetCode 背包问题总结

完全背包(物品数量无限)

LeetCode 377 组合总和 4

只需要知道有多少种组合(其实是求排列,与顺序有关),不需要知道是哪些组合。最开始以为和LeetCode 39很像,用回溯剪枝搜索,超时(回溯的复杂度很大)。这题不用知道具体的组合可以不用回溯,属于背包问题中的完全背包问题。
然后这题用C++的话,最后一个用例会超过INT_MAX,所以比java等要加一个判断条件。

用maximum[i] 表示背包里放了重量和为i 的物品时的方案数。
初始状态:maximum[0] 什么都没放,只有一种情况。则maximum[0]=1。其他情况还没有计算,都初始化为零。
式子:在到达maximum[i]之前的一步状态,可能有多种情况。比如在例子:

nums=[1,2,3] , target=4

中,到达maximum[3]方法可能是,[2]放入了1,[1,1]放入了1,[1]放入了2,[]放入了3。则maximum[3]的值就是把这几种方案数累加起来。
所以核心式子maximum[ i ]+=maximum[ i - nums[ j ] ]中,nums[ j ]就是当前放入的物品重量,maximum[ i-nums[ j ] ] 就是上一步状态的方案数。内层循环是遍历nums[ j ],看有哪些物品重量符合、可放入。
加条件maximum[ i ] + maximum[ i-nums[ j ] ] <INT_MAX 是因为C++中会溢出。

	int combinationSum4(vector<int>& nums, int target) {
        vector<int> maximum(target+1,0);
        maximum[0] = 1;
        sort(nums.begin(),nums.end());
        for(int i=1;i<=target;i++){
            for(int j=0;j<nums.size();j++){
                if(nums[j]>i) break;
                if(maximum[i] < INT_MAX-maximum[i-nums[j]]){
                    maximum[i]+=maximum[i-nums[j]];
                }             
            }
        }
        return maximum[target];        
    }
类似的题:
LeetCode 518

和上一题的区别就是,这题是组合问题,与顺序无关。写法上就产生了区别,需要去掉重复的组合,如[2,1] 和 [1,2] 。
大体思路都一样,不同的是,为了去重,需要按一定的顺序去选硬币。比如,已经选了2,就不能回头再去选1,这样就可以避免[2,1] 和 [1,2] 都选中。
可以看到,上一题中为什么能够选出[2,1] 和 [1,2],是因为它对于每一个小于等于target的目标重量(外循环),都把所有物品的重量试一遍有哪些放法(内循环),故在例子:

nums=[1,2,3] , target=4

中,i = 3时,遍历一遍nums,在nums[ j ]=1时,有maximum[ 3 ]+= maximum[ 2 ];在nums[ j ]=2时,有maximum[ 3 ]+= maximum[ 1 ] 。两种顺序都被计算进去。
而在本题中,为了不重复计算这两种相同的组合,我们改变内外循环,将外循环变成对物品重量的遍历(本题中具体为硬币面额),将内循环变成对target的遍历。这样就可以按一定的顺序选择硬币,当 i =3,外循环为 j =0, coins[ j ]=1 时,不可能选出[2,1]的组合,因为coins[ j ]=2还没有被选过。即只能按递增的顺序选择放入的硬币面额。

	int change(int amount, vector<int>& coins) {
        //组合问题,完全背包
        if(amount==0) return 1;
        vector<int> dp(amount+1,0);
        sort(coins.begin(),coins.end());
        dp[0]=1;
        
        for(int j=0; j<coins.size();j++){
            for(int i=1;i<=amount;i++){
                //可放入
                if(coins[j]<=i && dp[i]+dp[i-coins[j]]<INT_MAX){
                    dp[i]+=dp[i-coins[j]];
                }
            }
        }
        return dp[amount];
    }
LeetCode 322 零钱兑换

因为硬币可以重复使用,所以这是一个完全背包问题。完全背包只需要将 0-1 背包的逆序遍历 dp 数组改为正序遍历即可。
令dp[i]为组成钱数 i 需要的最少硬币数。则正序遍历时,dp[i] 与上一步(放入一个面值小于等于 i 的硬币之前)的状态有关。需要求最少硬币数,例如硬币面额有[1,2,5]时,则dp[i]=min(dp[i-1], dp[i-2], dp[i-5])+1 (i 大于等于1, 2, 5)。
初始化:dp[0] = 0 , 数组中的其他值初始化为比总面额大的数,这样可以通过判断最终计算的硬币数是否大于该面额来判断是否凑到了该面额。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //背包
        //dp[i]为组成i钱数需要的最少金币数
        //dp[i]=min(dp[i-1],dp[i-2],dp[i-5])+1
        //dp[0]=0
        if(amount==0) return 0;
        vector<int> dp(amount+1,amount+1);
        sort(coins.begin(),coins.end());
        int len=coins.size();
        dp[0]=0;
        for(int i=1;i<=amount;i++){
            for(int j=0;j<len && coins[j]<=i;j++){
                dp[i]=min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值