代码随想录算法训练营第三十八天|完全背包,518. 零钱兑换 II ,377. 组合总和 Ⅳ

完全背包

例题:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

区别:完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

公式推导:01背包问题是从二维数组压缩到一维数组,由于一维数组要保证每次取得物品不重合,遍历顺序也就变成了从后向前遍历。但是完全背包问题每种物品数量不限制数量,也就是说不需要防止重合情况,那问题就变成了一维数组或者说是滚动数组从前往后遍历

首先再回顾一下01背包的核心代码

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

而完全背包的物品是可以添加多次的,所以要从小到大去遍历

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

 注意:这里j什么要从weight[i]开始,因为当j小于weight[i]时,也就是当前容量不足以装下当前第i个物品,所以每次在遍历开始都要从weight[i]开始。

dp数组初始化:dp[j]=0,也就是没放物品时,不管容量多大,价值都是0;

举例推导dp公式:

背包最大重量为4。

物品为:

重量价值
物品0115
物品1320
物品2430

每件商品都有无限个!

问背包能背的物品最大价值是多少?

我们来走一遍流程:

只可以选物品0时:随着容量变大,不断放进物品0就行,直到装满,结果就出来了 ;

可以选物品0和物品1时:首先容量0-2时,物品1放不进,所有直接将上一步结果放入,这一步其实也就是解释了为什么二维数组可以被压缩,也解释了为什么j要从weight[i]开始;

        牢记dp[j]是代表j容量的背包填满,最大价值是多少。

 当容量变成3(j=weight[i])以后,dp[j]发生了什么,dp[j]有两个来源:

①不加入物品1,那就是和上一步结果一样,所以取dp[3];

②加入物品1,首先容量得腾出位置来放物品1,所以3-weight[1],也就是容量变成1了,当j=1时,dp[1]=15(最大价值是15),再加上物品1的价值value[1]=20,所以dp[3-weight[1]]+value[1]。进行最后一步,选出dp[3]和dp[3-weight[1]]+value[1]的最大值,就是当前容量填满放进去物品的最大价值。

        最后,j=4或者物品2也在选择范围时,就可以用公式:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

完整代码: 注意这里先开始遍历物品再开始遍历背包容量,反过来也可以,区别于一维01背包。

void test_CompletePack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    return dp[bagWeight];
}

518. 零钱兑换 II - 力扣(LeetCode)

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

思路:完全背包问题,amount就是背包容量,coins就是可以选择的物品。

解决:动态规划五部步曲

        1.确定dp[j]的含义;

        dp[j]:凑成总金额j的货币组合数为dp[j]。注意这里dp[j]求得是组合数,不是硬币的总额。

        2.确定递推公式;

        和494.目标和类似,dp[j]+=dp[j-coins[i]]

        3.确定dp初始化;

        还是和494.目标和类似,dp[0]=1;

代码随想录算法训练营第三十七天|1049. 最后一块石头的重量 II ,494. 目标和,474.一和零-CSDN博客

        4.确定遍历顺序;

        先遍历硬币,再遍历金额,里层遍历从前向后遍历,因为硬币可以重复用。

        5.举例推导dp数组。

输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

代码:

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

377. 组合总和 Ⅳ - 力扣(LeetCode)

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

思路:这里nums是可以重复的,可以转换成完全背包问题。

        ①物品:这里物品就是nums里面元素;

        ②背包:就是组合,但是这里要求的是组合数量,所以dp数组里不是放价值,是放组合的数量;

        拿示例1来说,就是需要背包物品总价值为4(j=4),从nums中选物品(可以重复)放入背包,有7种组合dp[j]。

解决:动态规划五步曲

        1.确定dp[j]含义;

        dp[j]就表示目标数为j的组合有dp[j]种。

        2.确定递推公式;

        和上题一样,dp[j]=dp[j]+dp[j-nums[i]]

        3.确定dp初始化;

        dp[j]初始都为0,dp[0]=1。

        4.确定遍历顺序;

                ①由于可以重复选取元素,所以这是一个完全背包问题

                ②其次组合顺序不同也算一种新组合,题目要的其实是排列

                循环方式就两种:外循环物品,内循环目标数;外循环目标数,内循环物品。

        如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

        所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。同样举个例子,当j=3时,我可以先取1再取2,或者先取2再取1,因为当前循环target没变,是通过遍历nums数组去组合。

        5.举例推导dp数组。

代码:

注意:C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1,0);
        dp[0]=1;
        for(int j=0;j<=target;j++){//遍历容量
            for(int i=0;i<nums.size();i++){//遍历物品
                if(j-nums[i]>=0&&dp[j] < INT_MAX - dp[j - nums[i]]){
                    dp[j]=dp[j]+dp[j-nums[i]];
                }
            }
        }
        return dp[target];
    }
};

        防止数组越界 要判断j-nums[i]>=0。

  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值