01 背包是动态规划的经典入门题,“01” 的含义很简单 ——每个物品要么选(1),要么不选(0),没有中间的多次选择情况。今天用最通俗的方式,带你搞懂它的解题思路和代码实现。
一、问题描述
给定 n 个物品,每个物品对应两个属性:重量 w[i] 和 价值 v[i];还有一个容量为 C 的背包。目标:选若干物品装入背包,要求总重量不超过容量 C,且总价值最大。
举个例子:
- 物品数量
n=3 - 重量
w = [2, 3, 4],价值v = [3, 4, 5] - 背包容量
C=5最优选择是拿前两个物品,总重量2+3=5,总价值3+4=7。
二、动态规划核心思路
动态规划的关键是 状态定义 和 状态转移方程,01 背包的核心逻辑就藏在这里面。
1. 状态定义
定义二维数组 dp[i][j]:前 i 个物品里选,装进容量为 j 的背包,能得到的最大价值。
2. 状态转移方程
对第 i 个物品,只有两种选择:选 或 不选。我们要选价值更大的那个方案。
- 不选第
i个物品:此时最大价值等于dp[i-1][j](前i-1个物品在容量j下的最优解)。 - 选第
i个物品:前提是背包容量够装它(j >= w[i]),此时价值等于dp[i-1][j - w[i]] + v[i](前i-1个物品在剩余容量下的最优解,加上当前物品的价值)。
综上,状态转移方程为:
plaintext
dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) (j >= w[i]时)
dp[i][j] = dp[i-1][j] (j < w[i]时,只能不选)
3. 初始化
- 当
i=0(没有物品可选):不管背包容量多大,dp[0][j] = 0。 - 当
j=0(背包容量为 0):不管有多少物品,dp[i][0] = 0。
三、代码实现(二维数组版)
以 Java 为例,逻辑清晰易懂:
java
运行
public class ZeroOneKnapsack {
public static int maxValue(int[] w, int[] v, int C) {
int n = w.length;
// 二维dp数组,n+1行(前0~n个物品),C+1列(容量0~C)
int[][] dp = new int[n + 1][C + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= C; j++) {
// 注意:数组下标从0开始,i对应第i-1个物品
if (j >= w[i-1]) {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - w[i-1]] + v[i-1]);
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][C];
}
public static void main(String[] args) {
int[] w = {2, 3, 4};
int[] v = {3, 4, 5};
int C = 5;
System.out.println(maxValue(w, v, C)); // 输出7
}
}
四、空间优化(一维数组版)
二维数组需要 O(n*C) 的空间,我们可以优化成 一维数组,空间复杂度降到 O(C)。
核心思路:用一维数组 dp[j] 表示容量 j 的背包能装的最大价值。注意遍历顺序:容量 j 必须从后往前遍历,避免同一个物品被重复选择。
优化后代码(Java):
java
运行
public static int maxValueOpt(int[] w, int[] v, int C) {
int n = w.length;
int[] dp = new int[C + 1];
for (int i = 0; i < n; i++) {
// 从后往前遍历,防止重复选同一物品
for (int j = C; j >= w[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[C];
}
五、总结
01 背包的核心是 **“选或不选” 的状态转移 **,记住两个关键点:
- 二维数组的状态定义和转移方程,是理解的基础;
- 一维数组优化的关键是倒序遍历容量,避免物品重复选取。
掌握 01 背包后,你就能轻松应对它的各种变种问题(如恰好装满背包、求方案数等)。
1933

被折叠的 条评论
为什么被折叠?



