初级背包问题,层层剖析为什么这样做。最好需要自己推演一遍。

初级背包问题

初级背包问题分为两种,01背包,和完全背包,两者的区别主要是在于,01背包一个物品只能选一次,完全背包则是同一个物品可以选择多次。

01背包

这里用力扣经典例题为例:

力扣416 分割等和子集为例: 416. 分割等和子集

相关企业

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

ini 复制代码
输入: nums = [1,5,11,5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

ini 复制代码
输入: nums = [1,2,3,5]
输出: false
解释: 数组不能分割成两个元素和相等的子集。

按照题意可总结题目要求,从数组里面几个值,使其和恰好为数组总和的一半,则满足分割子集的要求。

则把题目简化为 取其中几个数使其和为target,这里的target为sum(nums)的一半

我们这里把题目抽象成一个背包背景下的问题,方便初学者理解,如下:

能最多能承载 target kg 的背包,需要从数组里面选合适重量的数,并将其装入背包里面。

由题可知,我们需要的是和为 target的几个数,此时,target就可以抽象为 背包的最大容量,我们要尽量把背包装满,刚好能装满就刚好满足题意。不能装满也是一种最接近装满的状态。

现在我们来看代码

python 复制代码
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total%2 != 0:
            return False
        target = total // 2
        dp = [0]*(target+1)
        for num in nums:
            for j in range(target,num-1,-1):
                dp[j] = max(dp[j],dp[j-num]
                +num)
                
        return dp[target] == target
        
        

这里无法理解的话,最好自己做一个手动递推。

下面我们来解释代码,整个过程可以抽象为往背包里面放物品的过程。

  • 初始化dp数组,其含义最大承重为i的背包,在当前情况下,最多能放下多重的物品。所以我们只需要全部初始化为0即可。
  • 现在,我们可以想象这样一个情景,当按一定顺序发放一定质量的物品,你可以选择放入自己的背包或者不放入自己的背包。
  • 同时第二个循环,我们采取的是倒序遍历,为什么呢,我们假设是正序遍历,我们自己在推演的过程中会发现,之前的物品都被多加了不再限定在0或者1 之间。
  • 同时这里解释为什么是倒序遍历呢?我们这里维护一个固定载重的背包下,当前的限制条件下最大能放的质量,我们通过前一个状态,也就是dp[j-num],一定是上一轮已经维护好的能放最多的情况,所以我们与自己当前的情况相比,要不要选择接受这一样物品,从而维护自己达到当前轮的最好状态。
  • 基本思路如上,想要真正理解更需要自己一步步在实际过程中去推演。

完全背包

完全背包例题如:518 零钱兑换 二 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

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

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

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

示例 1:

ini 复制代码
输入: 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:

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

示例 3:

ini 复制代码
输入: amount = 10, coins = [10] 
输出: 1
python 复制代码
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        #注意完全背包 一维dp的时候,第二个循环是正序的
        #任选几个数和为 amount
        counts = 0
        dp = [0]*(amount+1)
        dp[0] = 1
        #定义dp数组 dp[j] 在j的容量下,有几种方法
        for coin in coins:
            for j in range(coin,amount+1):
                dp[j]+= dp[j-coin]
        return dp[amount]
        #TODO:
        #非常重要的问题,为什么这样子初始化???答:当容量为0 的时候 方案数为1,就是什么都不选。

这里主要解释为什么是正序?

当dp数组全部

假设 amount = 5, coins = [1, 2, 5]

1 0 0 0 0 0 (dp数组的初始状态)

当前我们取coins下标为0 的数,值为1,

我们采用正序遍历,可得过程为:

取 1

1 1 1 1 1 1

取2 1 1 2 2 3 3

取5

1 1 2 2 3 4

正序我们取当前的数,先行修改,将前面的组合数修改到最新,我们只需要考虑 dp[4-2] 的值,而非 dp[4-2-2],因为dp[2]中已经更新了dp[i-2]

下面我们再看个有关逆思维的问题。

\]: [leetcode.cn/problems/in...](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Finverse-coin-change%2Fdescription%2F "https://leetcode.cn/problems/inverse-coin-change/description/") 题解:[3592. 硬币面值还原 - 力扣(LeetCode)](https://link.juejin.cn?target=https%3A%2F%2Fleetcode.cn%2Fproblems%2Finverse-coin-change%2Fsolutions%2F3754221%2Fju-jue-sheng-ban-ying-tao-by-qiu-leng-li-tfjo%2F "https://leetcode.cn/problems/inverse-coin-change/solutions/3754221/ju-jue-sheng-ban-ying-tao-by-qiu-leng-li-tfjo/") 很多地方我感觉无法表述的很清楚,请同学们见谅,自己可以推演一下,理解会更深刻哦。

相关推荐
老葱头蒸鸡4 分钟前
(27)APS.NET Core8.0 堆栈原理通俗理解
算法
视睿10 分钟前
【C++练习】06.输出100以内的所有素数
开发语言·c++·算法·机器人·无人机
柠檬07111 小时前
matlab cell 数据转换及记录
算法
YuTaoShao2 小时前
【LeetCode 每日一题】2221. 数组的三角和
数据结构·算法·leetcode
little~钰2 小时前
树上倍增和LCA算法---上
算法
力扣蓝精灵2 小时前
今日分享 整数二分
算法
mc23562 小时前
5分钟学会微算法——Brian Kernighan 算法
算法
Excuse_lighttime2 小时前
除自身以外数组的乘积
java·数据结构·算法·leetcode·eclipse·动态规划
万添裁2 小时前
归并排序的三重境界
数据结构·算法
程序员三明治2 小时前
【重学数据结构】队列 Queue
数据结构·后端·算法