一、0-1 背包问题
📖 题目定义
有 n 个物品,每个物品只能用 一次,第 i 个物品有:
- 价值
value[i] - 重量
weight[i]
背包最大容量为W。
求在不超过背包容量的情况下,能放入的 最大价值。
🔎 解题思路
-
状态定义
dp[i][j]= 前i件物品在容量j下的最大价值。 -
状态转移
- 不选第 i 个物品:
dp[i][j] = dp[i-1][j] - 选第 i 个物品:
dp[i][j] = dp[i-1][j-weight[i]] + value[i] - 取最大值。
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]) - 不选第 i 个物品:
-
初始化
dp[0][..] = 0(没有物品时价值为 0)。dp[..][0] = 0(容量为 0 时价值为 0)。
-
关键点
- 为了避免一件物品被用多次,内层容量循环要倒序(一维压缩版时)。
✅ Java 代码(二维 DP + 一维优化)
class Solution01Knapsack {
public int knapsack01(int[] weights, int[] values, int W) {
int n = weights.length;
int[][] dp = new int[n + 1][W + 1];
// 二维DP模板
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= W; j++) {
dp[i][j] = dp[i - 1][j]; // 不选
if (j >= weights[i - 1]) {
dp[i][j] = Math.max(dp[i][j],
dp[i - 1][j - weights[i - 1]] + values[i - 1]);
}
}
}
return dp[n][W];
}
// 一维优化版
public int knapsack01Optimized(int[] weights, int[] values, int W) {
int n = weights.length;
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++) {
for (int j = W; j >= weights[i]; j--) { // 倒序避免重复选
dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
}
}
return dp[W];
}
}
二、完全背包问题
📖 题目定义
有 n 个物品,每个物品可以用 无限次,第 i 个物品有:
- 价值
value[i] - 重量
weight[i]
背包最大容量为W。
求在不超过背包容量的情况下,能放入的 最大价值。
🔎 解题思路
-
状态定义
dp[i][j]= 前i种物品在容量j下的最大价值。 -
状态转移
- 不选第 i 个物品:
dp[i][j] = dp[i-1][j] - 选第 i 个物品(可重复选):
dp[i][j] = dp[i][j-weight[i]] + value[i] - 取最大值。
dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]] + value[i])注意这里和 0-1 背包的区别:是从
dp[i][..]转移,而不是dp[i-1][..]。 - 不选第 i 个物品:
-
关键点
- 为了允许多次使用物品,内层容量循环要正序(一维压缩版时)。
✅ Java 代码(二维 DP + 一维优化)
class SolutionCompleteKnapsack {
public int completeKnapsack(int[] weights, int[] values, int W) {
int n = weights.length;
int[][] dp = new int[n + 1][W + 1];
// 二维DP模板
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= W; j++) {
dp[i][j] = dp[i - 1][j]; // 不选
if (j >= weights[i - 1]) {
dp[i][j] = Math.max(dp[i][j],
dp[i][j - weights[i - 1]] + values[i - 1]);
}
}
}
return dp[n][W];
}
// 一维优化版
public int completeKnapsackOptimized(int[] weights, int[] values, int W) {
int n = weights.length;
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++) {
for (int j = weights[i]; j <= W; j++) { // 正序允许重复使用
dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
}
}
return dp[W];
}
}
三、0-1 背包 vs 完全背包(对比表)
| 特性 | 0-1 背包 | 完全背包 |
|---|---|---|
| 每件物品次数 | 只能选 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) |
| 一维优化循环 | 倒序 (避免重复使用) | 正序 (允许重复使用) |
✅ 总结:
- 0-1 背包:物品只能用一次 → 倒序遍历容量
- 完全背包:物品可无限次使用 → 正序遍历容量
要不要我帮你再用「零钱兑换」这个经典题(LeetCode 322)把它和 完全背包 的关系对应起来(换个说法就是“求最少硬币数”的完全背包)?