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

初级背包问题

初级背包问题分为两种,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/") 很多地方我感觉无法表述的很清楚,请同学们见谅,自己可以推演一下,理解会更深刻哦。

相关推荐
先做个垃圾出来………几秒前
差分数组(Difference Array)
java·数据结构·算法
hansang_IR24 分钟前
【题解】洛谷 P4286 [SHOI2008] 安全的航线 [递归分治]
c++·数学·算法·dfs·题解·向量·点积
乐迪信息24 分钟前
乐迪信息:AI摄像机在智慧煤矿人员安全与行为识别中的技术应用
大数据·人工智能·算法·安全·视觉检测
多恩Stone1 小时前
【3DV 进阶-2】Hunyuan3D2.1 训练代码详细理解下-数据读取流程
人工智能·python·算法·3d·aigc
惯导马工2 小时前
【论文导读】IDOL: Inertial Deep Orientation-Estimation and Localization
深度学习·算法
老姜洛克2 小时前
自然语言处理(NLP)之n-gram从原理到实战
算法·nlp
CoovallyAIHub3 小时前
基于YOLO集成模型的无人机多光谱风电部件缺陷检测
深度学习·算法·计算机视觉
CoovallyAIHub3 小时前
几十个像素的小目标,为何难倒无人机?LCW-YOLO让无人机小目标检测不再卡顿
深度学习·算法·计算机视觉
怀旧,3 小时前
【C++】19. 封装红⿊树实现set和map
linux·c++·算法
往事随风去3 小时前
Redis的内存淘汰策略(Eviction Policies)有哪些?
redis·后端·算法