代码改变世界

解析01背包 - 教程

2025-09-27 15:21  tlnshuju  阅读(96)  评论(0)    收藏  举报

目录

一、问题分析与动态规划思路

 二、二维DP数组实现(基础版)

三、空间优化(一维DP数组)

四、示例验证

五、总结


01背包问题是动态规划中的经典问题,它的核心是在物品只能选或不选的约束下,求背包能容纳的最大价值(本题中为重量)。下面我们从思路分析、代码实现到空间优化,一步步深入讲解。

一、问题分析与动态规划思路


问题定义

有  n  个物品,每个物品有体积  v_i  和重量  w_i ,背包最大体积为  V 。每个物品只能选或不选,求背包能装的最大重量。

动态规划状态定义

我们定义  dp[i][j]  表示前  i  个物品,背包体积为  j  时的最大重量。

状态转移方程

对于第  i  个物品(注意数组下标从0开始,所以代码中是  i-1  索引),有两种选择:

- 不选: dp[i][j] = dp[i-1][j] (继承前  i-1  个物品的状态)。
- 选:如果当前物品体积  v_i ≤ j ,则  dp[i][j] = dp[i-1][j - v_i] + w_i (选了第  i  个物品,剩余体积  j - v_i  装前  i-1  个物品的最大重量,加上当前物品重量)。


综上,状态转移方程为:

dp[i][j] = \begin{cases}
dp[i-1][j] & \text{if } v_i > j \\
\max(dp[i-1][j],\ dp[i-1][j - v_i] + w_i) & \text{if } v_i \leq j
\end{cases}

 二、二维DP数组实现(基础版)


先看基础的二维动态规划实现,代码如下:

#include
class Solution {
public:
int knapsack(int V, int n, vector>& vw) {
// dp[i][j]:前i个物品,背包体积j时的最大重量
vector> dp(n + 1, vector(V + 1, 0));
for (int i = 1; i <= n; i++) {  // 遍历物品
for (int j = 1; j <= V; j++) {  // 遍历背包体积
if (vw[i-1][0] <= j) {  // 当前物品体积<=背包剩余体积,可选择“选”或“不选”
dp[i][j] = max(dp[i-1][j], dp[i-1][j - vw[i-1][0]] + vw[i-1][1]);
} else {  // 物品体积超过背包剩余体积,只能“不选”
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][V];
}
};

思路解释:

- 初始化  dp[0][j] (前0个物品,即没有物品)时,最大重量为0; dp[i][0] (背包体积为0)时,最大重量也为0。
- 外层循环遍历每个物品,内层循环遍历每个可能的背包体积,根据状态转移方程更新  dp  数组。

三、空间优化(一维DP数组)


观察状态转移方程可以发现, dp[i][j]  只依赖于  dp[i-1][...] (即上一层的状态)。因此可以将二维数组优化为一维数组,降低空间复杂度。

优化思路

用  dp[j]  表示背包体积为  j  时的最大重量。此时需要逆序遍历背包体积,避免同一物品被重复选择(保证每个物品只选一次,符合01背包的约束)。

优化后的代码如下:

#include
class Solution {
public:
int knapsack(int V, int n, vector>& vw) {
// dp[j]:背包体积为j时的最大重量
vector dp(V + 1, 0);
for (int i = 1; i = vw[i-1][0]; j--) {
dp[j] = max(dp[j[j dp[j - vw[i-1][0]] + vw[i-1][1]);
}
}
return dp[V];
}
};

思路解释:

- 逆序遍历背包体积  j ,使得每次更新  dp[j]  时, dp[j - v_i]  还是“上一层”(即不包含第  i  个物品)的状态,保证了每个物品只被选一次。
- 空间复杂度从 O(n \times V) 优化到 O(V),时间复杂度仍为 O(n \times V),满足题目“进阶 O(n \cdot v)”的要求。

四、示例验证


以题目中的示例1为例:

- 输入: V=10, n=2, vw=[[1,3],[10,4]] 
- 二维DP过程:
- 当  i=1 (第一个物品,体积1,重量3):
- 对于  j≥1 , dp[1][j] = max(dp[0][j], dp[0][j-1]+3) = 3 (因为  dp[0][j]  是0)。
- 当  i=2 (第二个物品,体积10,重量4):
- 当  j<10  时,只能不选, dp[2][j] = dp[1][j] = 3 ;
- 当  j=10  时, dp[2][10] = max(dp[1][10]=3, dp[1][0]+4=4) ,所以结果为4。
- 一维DP过程:
- 初始  dp = [0,0,0,...,0] (长度11)。
- 处理第一个物品(体积1,重量3):
- 逆序遍历  j=10  到  j=1 , dp[j] = max(dp[j[j dp[j-1]+3) ,最终  dp = [0,3,3,...,3] (共10个3)。
- 处理第二个物品(体积10,重量4):
- 逆序遍历  j=10  到  j=10 , dp[10] = max(3, dp[0]+4)=4 ,最终结果为4。

两种方法都能正确得到示例的返回值 4 。

五、总结


01背包问题的核心是动态规划的状态定义与转移,通过二维DP数组可以直观理解状态转移过程,再通过逆序遍历的一维DP数组实现空间优化。这种优化思路不仅适用于“重量最大化”的场景,也适用于“价值最大化”等变种01背包问题,是动态规划中必须掌握的经典技巧。