Skip to content
难度基础(★)
建议时长45分钟

15.2.8 背包问题(动态规划法)

动态规划解决 0-1 背包的关键是把“选哪些物品”转化为一张状态表:前 i 个物品,在容量为 t 的背包中,最多能获得多少价值。每个状态只依赖规模更小的状态,因此可以查表或递归记忆化。

状态定义

定义:

C(i,t)=前 i 个物品放入容量为 t 的背包时可获得的最大价值

这里的 i 表示只考虑前 i 个物品,t 表示当前容量限制。答案就是 C(n,W)

符号含义
vii 个物品价值
wii 个物品重量
W背包总容量
C(i,t)i 件物品、容量 t 下的最大价值

递推式:放不下、放或不放

动态规划的代码填空几乎都来自递推式。

C(i,t)={0,i=0 或 t=0C(i1,t),t<wimax{C(i1,t), C(i1,twi)+vi},twi

三种情况要逐一理解:

情况含义代码思路
i=0t=0没物品或没容量,价值为 0C[i][t] = 0
t<wii 件太重,放不下C[i][t] = C[i-1][t]
twii 件可以尝试放入比较“不放”和“放”

“不放第 i 件”时,价值是 C(i1,t);“放第 i 件”时,剩余容量为 twi,前面只能从前 i1 件中选,所以价值是 C(i1,twi)+vi

最优子结构与重叠子问题

0-1 背包适合动态规划,是因为它同时具备两个特征:

  • 最优子结构:若最优解包含第 i 件物品,则剩余部分必须是“前 i1 件物品、容量 twi”的最优解;若不包含第 i 件,则答案来自“前 i1 件、容量 t”。
  • 重叠子问题:不同选择路径会反复计算同一个 C(i,t),因此需要数组保存中间结果。

这也解释了为什么动态规划代码里会出现二维数组 Cdp。数组不是普通输入,而是保存中间最优值的表。

自顶向下与自底向上

实现方式行为代码特征复杂度直觉
自顶向下C(n,W) 开始递归求小状态函数递归、数组初值如 -1 表示未计算有记忆化时避免重复;无记忆化会很大
自底向上C(0,0) 等小状态开始填表双层循环遍历物品和容量通常 O(nW)

字幕里的真题代码使用了类似自顶向下的写法:如果 C[i][j] != -1,说明这个状态已经算过,直接返回;否则按递推式计算并存入 C[i][j]

代码填空如何从递推式翻译

假设函数 maxValue(v, w, i, j) 表示 C(i,j),二维数组 C[i][j] 保存结果。

代码空对应含义典型填法
已计算状态直接返回C[i][j] != -1 时不用重复算return C[i][j];
边界条件没物品或没容量C[i][j] = 0;
能否放第 i容量不小于当前重量j >= w[i] 或按题目下标写 j >= w[i-1]
放第 i 件的价值剩余容量加当前价值maxValue(v,w,i-1,j-w[i]) + v[i]
取较大者比较放与不放if (temp > C[i][j]) C[i][j] = temp;

下标是最容易出错的地方。如果题干物品编号从 1 开始,公式里的 wi 对应 w[i];如果 C 数组从 0 开始,常会写成 w[i-1]v[i-1]。考试要跟随题干代码,不要机械套模板。

小例子:为什么比较两种方案

假设当前考虑第 i 件,容量为 t。如果放它,价值增加 vi,但容量减少 wi;如果不放,容量保持 t,但不能得到 vi

mermaid
flowchart TD
  A["状态 C(i,t)"] --> B["不选第 i 件: C(i-1,t)"]
  A --> C["选第 i 件: C(i-1,t-w_i)+v_i"]
  B --> D["取较大值"]
  C --> D

这就是动态规划比单纯贪心可靠的地方:它不是只看当前物品“看起来值不值”,而是比较两个子问题的最优结果。

实例题的处理方式

字幕里提到,若题目给五件物品、容量为 11,要求最大价值,直接手填完整二维表可能很繁琐。考试中可以结合方案列举和容量约束快速判断,但写代码填空时仍然要回到递推式。

对于实例运行结果题,建议:

  1. 先看容量是否较小,能否列举可行组合。
  2. 按价值较大的物品开始尝试,但不要把这当成贪心证明。
  3. 每个候选方案都检查总重量是否超过容量。
  4. 在可行方案中比较总价值。

动态规划与其他方法的取舍

方法能否保证 0-1 背包最优代价适合考点
贪心通常不能策略判断、满意解
回溯能,若完整搜索并正确剪枝最坏 O(2n)解空间树、剪枝、回退
动态规划常见 O(nW) 时间和空间递推式、状态表、代码填空

动态规划用空间换时间:二维数组保存中间结果,避免重复求同一个子问题。这就是它相对于朴素递归的技术迭代价值。

单选
0-1 背包中,当第 $i$ 件物品重量 $w_i$ 不超过当前容量 $t$ 时,正确的状态转移是哪一个?

本节小结

动态规划法用状态表表达 0-1 背包的最优子结构。代码填空最重要的是把递推式翻译成数组访问:容量不足只能不选,容量足够就比较选与不选,已经计算过的状态直接返回。