程序员面试宝典

一站式面试准备平台

返回分类
algorithms高级

背包问题系列

深入理解 0-1 背包、完全背包的原理,以及如何从问题中抽象出 DP 模型

2026-04-15
阅读时间: 8分钟

背包问题系列

背包问题是 DP 中的经典题型。面试官想看到的不是你会套模板,而是你能从问题中抽象出 DP 模型,理解为什么这样定义状态

面试考察点

面试官通过这道题想考察:
1. 你是否能正确抽象出 DP 的状态
2. 你是否能理解"选"和"不选"的决策过程
3. 你是否能区分 0-1 背包和完全背包
4. 你是否能进行空间优化

一、0-1 背包问题

1.1 问题定义(小白解释)

面试官追问:"什么是 0-1 背包?"

问题描述:
- 有一个容量为 W 的背包
- 有 n 件物品,每件物品有重量 w[i] 和价值 v[i]
- 每件物品只能选一次(0 或 1)
- 问:如何选择物品使得不超过容量 W 的情况下价值最大?

示例:
背包容量 W = 8
物品:
  A: 重量 3,价值 30
  B: 重量 4,价值 50
  C: 重量 5,价值 60

选择 A+B:重量 3+4=7,价值 30+50=80
选择 A+C:重量 3+5=8,价值 30+60=90 ✓
选择 B+C:重量 4+5=9,超了!

1.2 如何定义状态?(核心)

面试官追问:"你怎么定义 DP 的状态?"

状态定义:
dp[i][j] = 考虑前 i 件物品,背包容量为 j 时的最大价值

为什么这样定义?
因为对于每件物品,我们只有两个选择:选或不选

关键理解:
dp[i][j] 依赖于 dp[i-1][j] 和 dp[i-1][j-w[i]]

- dp[i-1][j]:不选第 i 件物品
- dp[i-1][j-w[i]] + v[i]:选第 i 件物品(需要腾出 w[i] 的空间)

这就是"最优子结构"!

1.3 状态转移方程

dp[i][j] = max(
    dp[i-1][j],              // 不选第 i 件物品
    dp[i-1][j-w[i]] + v[i]  // 选第 i 件物品
)

边界条件:
- dp[0][j] = 0(没有物品可选)
- dp[i][0] = 0(容量为 0)
- j < w[i] 时,只能不选

1.4 图解过程

物品:重量 [3, 4, 5],价值 [30, 50, 60]
容量 W = 8

填表过程:

dp[0][*] = 0(没有物品)
dp[*][0] = 0(容量为0)

填 dp[1][*](考虑物品 A):
- j=0..2: j < 3,只能不选,dp[1][j] = 0
- j=3: max(dp[0][3], dp[0][0]+30) = max(0, 30) = 30
- j=4: max(dp[0][4], dp[0][1]+30) = max(0, 30) = 30
...
- j=8: max(dp[0][8], dp[0][5]+30) = max(0, 30) = 30

填 dp[2][*](考虑物品 A, B):
- j=3: max(dp[1][3], dp[1][-1]+50) = max(30, -∞) = 30
- j=4: max(dp[1][4], dp[1][0]+50) = max(30, 50) = 50
- j=7: max(dp[1][7], dp[1][3]+50) = max(30, 30+50) = 80
- j=8: max(dp[1][8], dp[1][4]+50) = max(30, 50) = 50

最终 dp[3][8] 就是答案

二、0-1 背包的变形

2.1 分割等和子集(LeetCode 416)

考察点:0-1 背包的变形

问题:能否把数组分成两个和相等的子集?

转换:
- 总和 sum,如果能平分,每个子集和为 sum/2
- 问题变成:能否从数组中选若千元素,和为 sum/2?

这就是 0-1 背包!
- 背包容量 = sum/2
- 每件物品(数字)只能用一次
- 问:能否装满背包?

2.2 目标和(LeetCode 494)

考察点:状态定义的技巧

问题:在数组每个数前加 + 或 -,使结果等于 target

转换:
- 加 + 的数为 P,减 - 的数为 N
- P + N = sum
- P - N = target
- 解方程:P = (sum + target) / 2

问题变成:选出若干数,和为 P,有多少种选法?

这就是 0-1 背包的"计数"版本!

三、完全背包问题

3.1 和 0-1 背包的区别

面试官追问:"完全背包和 0-1 背包有什么区别?"

0-1 背包:每件物品只能选一次
完全背包:每件物品可以选无限次

示例:
有硬币 1 元、2 元、5 元,要凑 10 元

0-1 背包:每种硬币只有 1 个
完全背包:每种硬币有无限个

在 DP 中:
- 0-1 背包:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)
            用上一行的状态(不重复选同一物品)
- 完全背包:dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v)
            用本行的状态(可以重复选同一物品)

3.2 零钱兑换 II(LeetCode 518)

考察点:完全背包的计数问题

问题:凑成 amount 的硬币组合数

物品:硬币面额
背包容量:amount
每种硬币无限(完全背包)

状态定义:
dp[i] = 凑成金额 i 的方式数

状态转移:
dp[j] += dp[j - coin] for coin in coins

初始化:
dp[0] = 1(凑 0 元有一种方式:不选任何硬币)

四、面试总结

背包问题的本质

┌─────────────────────────────────────────────────┐
│                    背包问题                       │
├─────────────────────────────────────────────────┤
│                                                  │
│  0-1 背包:每件物品只能选一次                    │
│  完全背包:每件物品可以选多次                    │
│                                                  │
│  状态定义:dp[i][j] = 考虑前 i 件,容量 j 的最优 │
│                                                  │
│  0-1 背包转移:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w]+v) │
│  完全背包转移:dp[i][j] = max(dp[i-1][j], dp[i][j-w]+v)   │
│                                                  │
│  空间优化:dp[j] 从后往前(0-1)                │
│            dp[j] 从前往后(完全)               │
│                                                  │
└─────────────────────────────────────────────────┘

面试能加分的回答

1. 能解释为什么这样定义状态
   "对于每件物品只有选或不选两种情况"

2. 能解释状态转移
   "选或不选,取价值更大的"

3. 能解释 0-1 和完全背包的区别
   "0-1 用上一行状态,完全用本行状态"

4. 能解释空间优化
   "因为 dp[j] 只依赖 dp[j-w],可以压缩"

相关题目

相关标签