研习代码 day38 | 动态规划——完全背包问题(一维滚动数组)

一、完全背包问题与其解法

        1.1 完全背包问题

        假设有一个可装载重量为W的背包,以及一组物品,每种物品都有一个重量和一个价值。要求在不超过背包容量的前提下,选择一些物品放入背包中,使得背包中物品的总价值最大化。

        与0-1背包问题不同的是,完全背包问题允许同一种物品可以选择多次放入背包中。也就是说,对于每一种物品,可以选择将它放入背包中0次、1次、2次......直到放满为止。

        1.2 完全背包问题与0-1背包问题解法区别

        完全背包问题与0-1背包问题的解法大致类似,但因其二者的特性差别,导致有部分差别。

        1.2.1 遍历背包容量的顺序

       使用滚动一维数组时
        0-1 背包:因需保证每个物品最多被添加一次,所以遍历背包容量时是倒序
        完全背包:可以对每个物品添加多次,所以遍历背包容量时是正序(可以手写比较一下)


        

                     倒序遍历背包容量                                                  正序遍历背包容量

        1.2.2 内外的遍历顺序是否可交换

        0-1 背包:因为每个物体至多只能使用一次,遍历背包容量时是倒序,也就是从右往左的方向,但总体的遍历是依据之前的情况去递推后续节点,也就是从左往后推导,此时需要外侧有一个正序遍历的循环,所以需先正序遍历物体,后逆序遍历背包容量。

        完全背包:见排列与组合部分的辨析。

二、完全背包问题的应用例题

        2.1 零钱兑换 II

        2.1.1 题目

        给你一个整数数组 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 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000
        2.1.2 题目链接

        518.零钱兑换 II

        2.1.3 解题思路和过程想法

        (1)解题思路

        # 分析:无限的物体,求凑齐特定值的方法数----> 完全背包问题
        # 数组:dp[j] 表示构成总金额 j 的组成方法数
        # 递推关系:dp[j] += dp[j-coin[i]]
        # 初始化:dp[0]若是初始化为0,则后续全是0,无法进行
                        dp[0] = 1
        # 举例递推:纯完全背包内外循环顺序可调换,但遍历背包需正序,这样才能表示物品可以添加多次
                        for coin in coins:
                              for j in range(coin,amount+1):
                                  dp[j] += dp[j-coin]

        (2)过程想法

        上述是“组合”问题,注意和“排列”对比一下

        2.1.4 代码
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        # 分析:无限的物体,求凑齐特定值的方法数----> 完全背包问题

        # 数组:dp[j] 表示构成总金额 j 的组成方法数
        dp = [0] * (amount+1)

        # 递推关系:dp[j] += dp[j-coin[i]]

        # 初始化:dp[0]若是初始化为0,则后续全是0,无法进行
        dp[0] = 1

        # 举例递推:纯完全背包内外循环顺序可调换,但遍历背包需正序,这样才能表示物品可以添加多次
        for coin in coins:
            for j in range(coin,amount+1):
                dp[j] += dp[j-coin]

        return dp[amount]

        2.2 组合总和 IV

        2.2.1 题目

        给你一个由 不同 整数组成的数组 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)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入:nums = [9], target = 3
输出:0

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

进阶:如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

        2.2.2 题目链接

        377.组合总和 IV

        2.2.3 解题思路和过程想法

        (1)解题思路

        # 分析:由多种数量为无限的物体,组成一个固定值,求其排列方法数---> 完全背包问题
        # 数组:dp[j] 固定值 j 的排列方法数
        # 递推关系:dp[j] += dp[j-nums[i]]
        # 初始化
                        dp[0] = 1
        # 举例递推:完全背包的排列方法数需先遍历背包,后遍历物体
                        for j in range(1,target+1):     # 遍历背包
                              for i in range(len(nums)):  # 遍历物体
                                     if j >= nums[i]:
                                            dp[j] += dp[j-nums[i]]

        (2)过程想法

                上述是“排列”问题,注意和“组合”对比一下

        2.2.4 代码
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 分析:由多种数量为无限的物体,组成一个固定值,求其排列方法数---> 完全背包问题

        # 数组:dp[j] 固定值 j 的排列方法数
        dp = [0] * (target+1)

        # 递推关系:dp[j] += dp[j-nums[i]]

        # 初始化
        dp[0] = 1

        # 举例递推:完全背包的排列方法数需先遍历背包,后遍历物体
        for j in range(1,target+1):     # 遍历背包
            for i in range(len(nums)):  # 遍历物体
                if j >= nums[i]:
                    dp[j] += dp[j-nums[i]]
        
        return dp[target]

三、完全背包问题的排列与组合问题

              交换内外循环顺序 :先遍历物体可保证同数量的情况仅计算一次,
                                               先遍历背包可保证同数量但有不同出现的顺序都能被计算一次。

        组合:遍历物体的循环是在外层,遍历背包容量的循环是在内层。
        排列:遍历背包容量的循环是在外层,遍历物品的循环是在内层。
        

四、常用套路总结

        排列问题:1+2+1 和 2+1+1 是两种不同方法
        
        递推关系:求组成的方法数——————dp[j] += dp[j-i],
                          求背包装载的最大“价值” ——dp[j] = max(dp[j], dp[j-weight[i]]+value[i])
                          求背包装载的最少“数量” ——dp[j] = min(dp[j], dp[j-weight[i]]+1)
        初始化:求组成的方法数——dp[0] = 1
                       其他值初始化———初始化不影响覆盖的值,求最大-->初始化最小;
                                                                                               求最小-->初始化无穷大值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值