背包问题:别死背模板了,看看这层「剩余空间」的逻辑
背包问题:别死背模板了,看看这层「剩余空间」的逻辑
蔚蓝WEILANX背包问题:别死背模板了,看看这层「剩余空间」的逻辑
很多人刷 DP(动态规划)的时候,第一站就是背包。网上的题解一搜一大把,公式背得滚瓜烂熟,结果换个背景、换个限制条件,脑子就又空了。
其实背包问题的本质不是那几个 $i$ 和 $j$,而是你对 「剩余空间」 的调度,是你怎么在「拿」与「不拿」之间做那场贪心做不到的博弈。
今天不讲废话,直接把 01 背包和完全背包这点事儿,从底层逻辑到 Java 实现给你拆透。
这种题,一眼就是「背包」
刷题刷多了,你会发现背包题的指纹很明显。
只要题目里出现了 「资源受限」(比如负重不够了、目标和是固定的、零钱就那么多)和 「选还是不选」(拿这个物品还是那个物品),基本就跑不出背包的圈子。
- 01 背包:东西就一个,拿了就没了,主打一个独一无二。
- 完全背包:东西无限供应,只要你装得下,拿一百个也没人管,主打一个无限续杯。
01 背包:别被二维数组绕晕,它只是辅助工具
很多人初学的时候死磕 dp[i][j],觉得这二维矩阵太抽象。其实它就是一张收益记录表。
二维的直观:状态的积累
dp[i][j] 的含义非常朴素:我在面对前 i 个物品,且背包还剩 j 容量时,我能捞到的最大价值。
核心就这一句:
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])
如果不拿第 i 个,我的价值就是上一行的同位置;如果拿了,我的价值就是「给这个物品腾空后的剩余价值」加上「这个物品本身的价值」。
1 | // 这是最老实的写法,适合理解逻辑 |
一维的优雅:为什么要倒着遍历?
等你理解了二维,你就会发现:计算 dp[i] 的时候,其实只用到了 dp[i-1]。那我们直接用一行数组滚过去不就行了?
但这里有个坑: 如果你正着更新容量,你在算 dp[j] 的时候,前面的 dp[j - weight[i]] 可能已经是这一轮更新过后的结果了——这等于你把同一个物品拿了好几次。
所以,01 背包压缩到一维时,必须倒序遍历。这是为了保证你参考的永远是「上一层」还没拿过这个物品的状态。
1 | for (int i = 0; i < weight.length; i++) { |
完全背包:把 01 背包反过来写,就是无限选取
完全背包和 01 背包唯一的代码差别,就在那个倒序变正序。
为什么?因为完全背包就是要让前面的更新影响后面。你在计算 dp[j] 时,引用的 dp[j - weight[i]] 如果已经是这一轮更新过的,正好表示「我可以再拿一次物品 i」。
这就是为什么 正序遍历容量 = 无限选取。
1 | for (int i = 0; i < weight.length; i++) { |
陷阱:组合还是排列?
这是一个面试高频坑点。如果题目问的是「凑齐 $S$ 有多少种方法」:
- 先遍历物品,再遍历容量:得到的是 「组合数」(比如 {1, 5} 和 {5, 1} 只算一种)。
- 先遍历容量,再遍历物品:得到的是 「排列数」(比如爬楼梯,先走 1 步再走 2 步,和先 2 再 1 是不一样的)。
那些披着狼皮的背包题
机试的时候,没人会直接告诉你这是「01 背包」。
- 最后一块石头的重量 II:就是让你把石头分成两堆,让两堆的重量差距最小。等价于:容量为
sum/2的背包,能装下的最大重量。 - 目标和:看起来是加减法,其实是求「装满特定容量背包的方法数」。公式变成
dp[j] += dp[j - nums[i]]。 - 零钱兑换:经典的完全背包。求最少硬币数,初始化就要用
amount + 1来表示无穷大,不然min函数没法玩。
碎碎念的总结
01 背包像人生,错过了就没了,所以要谨慎,看准了再拿; 完全背包像食堂,只要饭卡有钱,大鸡腿你想刷几次刷几次。
别去死记那几个嵌套循环的顺序,多想想:我这一步更新,到底需要用到「没拿过」的状态,还是「拿过也行」的状态? 想通了这一点,背包问题你就真的毕业了。
如果你在写 Java 的时候还遇到了 long 溢出(求方法数时常有)或者是 INT_MAX 加一变负数的坑,别怀疑,那是你对边界还没刻进 DNA 里。
工具给你了,剩下的就是去 LeetCode 上找几个题,把这些逻辑擦亮。
六、 实战演练:LeetCode 题单
刷题是掌握 DP 的唯一捷径。以下是灵神整理的经典背包题目,建议从基础题开始,逐步攻克。
§ 6.1 0-1 背包(选或不选)
- 416. 分割等和子集
- 494. 目标和
- 2915. 和为目标值的最长子序列的长度
- 2787. 将一个数字表示成幂的和的方案数
- 3180. 执行操作可获得的最大总奖励 I
- 474. 一和零(二维费用)
- 1049. 最后一块石头的重量 II
- 879. 盈利计划
- 2742. 给墙壁刷油漆(至少装满)
- 2518. 好分区的数目
§ 6.2 完全背包(重复选取)
这篇文章没用那些高大上的学术名词,希望能让你对 DP 的理解多一点「人的温度」。
















