01背包问题 - 动态规划最优解法(Java实现)
01背包问题 - 动态规划最优解法(Java实现)
问题描述
01背包问题是经典的动态规划问题:
- 有一个容量为
W
的背包 - 有
n
个物品,每个物品有重量weights[i]
和价值values[i]
- 每个物品只能选择一次(要么选0个,要么选1个)
- 目标:在不超过背包容量的前提下,使背包中物品总价值最大
最推荐的Java解决方案
public static int knapsack(int[] weights, int[] values, int capacity) {int n = weights.length;// dp[i][w]表示前i个物品放入容量为w的背包中能获得的最大价值int[][] dp = new int[n + 1][capacity + 1];// 填充dp表for (int i = 1; i <= n; i++) {for (int w = 1; w <= capacity; w++) {// 当前物品的重量和价值(注意索引转换)int weight = weights[i - 1];int value = values[i - 1];if (weight <= w) {// 当前物品重量不超过背包容量,可以选择// 取不选择和选择当前物品的最大值dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weight] + value);} else {// 当前物品重量超过背包容量,不能选择dp[i][w] = dp[i - 1][w];}}}return dp[n][capacity];
}
关键变量详解
变量 | 含义 | 作用 |
---|---|---|
dp[i][w] | 前i个物品放入容量为w的背包能获得的最大价值 | 状态定义,存储子问题的最优解 |
n | 物品总数 | 确定dp数组的行数 |
capacity | 背包容量 | 确定dp数组的列数 |
weight | 当前考虑物品的重量 | 判断是否能放入背包 |
value | 当前考虑物品的价值 | 计算选择该物品后的总价值 |
i | 当前考虑到第i个物品 | 循环控制变量,表示子问题规模 |
w | 当前考虑的背包容量 | 循环控制变量,表示子问题的容量限制 |
完整可视化演示
使用测试用例:
- 物品信息:[重量: [2,3,4,5], 价值: [1,4,5,7]]
- 背包容量:8
初始化阶段
DP表初始状态(dp[i][w] = 0):w=0 w=1 w=2 w=3 w=4 w=5 w=6 w=7 w=8
i=0 0 0 0 0 0 0 0 0 0
i=1 0 0 0 0 0 0 0 0 0
i=2 0 0 0 0 0 0 0 0 0
i=3 0 0 0 0 0 0 0 0 0
i=4 0 0 0 0 0 0 0 0 0
逐步填充过程
第1轮:考虑物品1 (重量=2, 价值=1)
当前物品:物品1 [重量=2, 价值=1]
填充 dp[1][1]:
weight=2 > w=1
→ 分支1:不能选择dp[1][1] = dp[0][1] = 0
填充 dp[1][2]:
weight=2 <= w=2
→ 分支2:可以选择- 不选:
dp[0][2] = 0
- 选择:
dp[0][2-2] + 1 = 0 + 1 = 1
dp[1][2] = Math.max(0, 1) = 1
填充 dp[1][3] 到 dp[1][8]:
- 都满足
weight=2 <= w
,选择物品1更优 dp[1][3] = dp[1][4] = ... = dp[1][8] = 1
第1轮完成后:w=0 w=1 w=2 w=3 w=4 w=5 w=6 w=7 w=8
i=0 0 0 0 0 0 0 0 0 0
i=1 0 0 1 1 1 1 1 1 1 ← 新填充
i=2 0 0 0 0 0 0 0 0 0
i=3 0 0 0 0 0 0 0 0 0
i=4 0 0 0 0 0 0 0 0 0
第2轮:考虑物品2 (重量=3, 价值=4)
当前物品:物品2 [重量=3, 价值=4]
关键计算 dp[2][5]:
weight=3 <= w=5
→ 分支2:可以选择- 不选:
dp[1][5] = 1
- 选择:
dp[1][5-3] + 4 = dp[1][2] + 4 = 1 + 4 = 5
dp[2][5] = Math.max(1, 5) = 5
← 关键比较分支
第2轮完成后:w=0 w=1 w=2 w=3 w=4 w=5 w=6 w=7 w=8
i=0 0 0 0 0 0 0 0 0 0
i=1 0 0 1 1 1 1 1 1 1
i=2 0 0 1 4 4 5 5 5 5 ← 新填充
i=3 0 0 0 0 0 0 0 0 0
i=4 0 0 0 0 0 0 0 0 0
第3轮:考虑物品3 (重量=4, 价值=5)
当前物品:物品3 [重量=4, 价值=5]
关键计算 dp[3][7]:
weight=4 <= w=7
→ 分支2:可以选择- 不选:
dp[2][7] = 5
- 选择:
dp[2][7-4] + 5 = dp[2][3] + 5 = 4 + 5 = 9
dp[3][7] = Math.max(5, 9) = 9
← 关键比较分支
第3轮完成后:w=0 w=1 w=2 w=3 w=4 w=5 w=6 w=7 w=8
i=0 0 0 0 0 0 0 0 0 0
i=1 0 0 1 1 1 1 1 1 1
i=2 0 0 1 4 4 5 5 5 5
i=3 0 0 1 4 5 6 9 9 9 ← 新填充
i=4 0 0 0 0 0 0 0 0 0
第4轮:考虑物品4 (重量=5, 价值=7)
当前物品:物品4 [重量=5, 价值=7]
关键计算 dp[4][8]:
weight=5 <= w=8
→ 分支2:可以选择- 不选:
dp[3][8] = 9
- 选择:
dp[3][8-5] + 7 = dp[3][3] + 7 = 4 + 7 = 11
dp[4][8] = Math.max(9, 11) = 11
← 关键比较分支
填充 dp[4][4]:
weight=5 > w=4
→ 分支1:不能选择dp[4][4] = dp[3][4] = 5
最终DP表:w=0 w=1 w=2 w=3 w=4 w=5 w=6 w=7 w=8
i=0 0 0 0 0 0 0 0 0 0
i=1 0 0 1 1 1 1 1 1 1
i=2 0 0 1 4 4 5 5 5 5
i=3 0 0 1 4 5 6 9 9 9
i=4 0 0 1 4 5 7 10 11 11 ← 最终答案
代码分支覆盖分析
通过上述演示用例,我们完全覆盖了Java代码的所有分支:
分支1:weight > w
(不能选择)
- 触发位置:dp[1][1], dp[4][4] 等
- 代码执行:
dp[i][w] = dp[i-1][w]
- 说明:当前物品太重,无法放入背包
分支2:weight <= w
(可以选择)
- 触发位置:dp[1][2], dp[2][5], dp[3][7], dp[4][8] 等
- 代码执行:
dp[i][w] = Math.max(dp[i-1][w], dp[i-1][w-weight] + value)
Math.max的比较分支
- 不选更优:dp[2][3] = max(1, 4) → 选择4
- 选择更优:dp[2][5] = max(1, 5) → 选择5
- 关键比较:dp[4][8] = max(9, 11) → 选择11
算法复杂度
- 时间复杂度:O(n × capacity),需要填充 n×capacity 个状态
- 空间复杂度:O(n × capacity),需要存储 n×capacity 个状态
最终答案
通过动态规划,我们得到最优解是 11,对应的最优选择方案是:
- 选择物品2(重量=3,价值=4)
- 选择物品4(重量=5,价值=7)
- 总重量:3+5=8 ≤ 8 ✓
- 总价值:4+7=11
这个解法简洁易懂,逻辑清晰,是01背包问题的最推荐实现方式。