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