0-1 背包问题:有限容量下的最大价值
1. 问题定义
0-1 背包问题是最经典的动态规划问题,描述如下:
给定
n个物品,每个物品有重量w[i]和价值v[i],背包最大容量为C。每个物品只能选择 “放入” 或 “不放入” 背包(0-1 选择),求在背包容量限制下,能装入的最大价值。
例如:物品重量 [1,2,3],价值 [6,10,12],背包容量 5,最大价值为 22(选重量 2 和 3 的物品,价值 10+12=22)。
2. 核心求解策略
0-1 背包是多项式时间可解的问题(P 类问题),核心解法是动态规划,且可通过空间优化降低复杂度:
基础 DP 思路(二维数组)
-
状态定义:
dp[i][j]表示 “前i个物品,背包容量为j时的最大价值”。 -
状态转移:
- 不选第
i个物品:dp[i][j] = dp[i-1][j]; - 选第
i个物品(需满足j ≥ w[i]):dp[i][j] = max(dp[i][j], dp[i-1][j-w[i]] + v[i])。
- 不选第
空间优化(一维数组)
二维 DP 的空间复杂度为 O(n*C),可优化为 O(C):
- 状态定义:
dp[j]表示 “背包容量为j时的最大价值”; - 状态转移:逆序遍历容量
j(避免重复选择同一物品),dp[j] = max(dp[j], dp[j-w[i]] + v[i])。
3. Java 代码实现(一维 DP 优化版)
import java.util.Arrays;
/**
* 0-1背包问题 - 一维动态规划优化解法
* 空间复杂度O(C),时间复杂度O(n*C)
*/
public class ZeroOneKnapsack {
/**
* 求解0-1背包最大价值
* @param weights 物品重量数组
* @param values 物品价值数组
* @param capacity 背包最大容量
* @return 最大价值
*/
public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
// dp[j]:容量为j的背包能装的最大价值
int[] dp = new int[capacity + 1];
// 初始化:容量为0时,价值为0(数组默认值为0,无需额外初始化)
Arrays.fill(dp, 0);
// 遍历每个物品
for (int i = 0; i < n; i++) {
int w = weights[i];
int v = values[i];
// 逆序遍历容量:避免重复选择同一物品(0-1特性)
for (int j = capacity; j >= w; j--) {
// 状态转移:选或不选当前物品,取最大值
dp[j] = Math.max(dp[j], dp[j - w] + v);
}
}
return dp[capacity];
}
public static void main(String[] args) {
// 测试用例:物品重量[1,2,3],价值[6,10,12],背包容量5
int[] weights = {1, 2, 3};
int[] values = {6, 10, 12};
int capacity = 5;
int maxValue = knapsack(weights, values, capacity);
System.out.println("0-1背包最大价值:" + maxValue); // 预期结果:22
}
}
4. 代码详解
-
数组初始化:
dp数组长度为capacity+1,初始值全为 0(容量为 0 时价值为 0)。 -
物品遍历:外层循环遍历每个物品,获取当前物品的重量
w和价值v。 -
逆序容量遍历:
- 逆序遍历是 0-1 背包的核心(区别于完全背包的正序),确保每个物品仅被选择一次;
- 仅遍历
j ≥ w的容量(容量不足时无法选择当前物品); - 状态转移:比较 “不选当前物品(dp [j])” 和 “选当前物品(dp [j-w]+v)” 的价值,取最大值。
-
结果返回:
dp[capacity]即为 “背包容量为capacity时的最大价值”。