在算法学习的道路上,01背包问题是一个绕不开的经典。今天我们聚焦代码随想录day 35中关于力扣01背包问题的二维数组解法,深入探讨其原理,并分享一些实战中的避坑经验。这种方法是理解背包问题的基础,为后续优化打下坚实的基础。很多大厂面试也喜欢考察背包问题,所以掌握它是非常有必要的。
问题场景重现
假设你是一个小偷,背着一个容量为bagSize的背包,闯入一家商店。商店里有n件物品,每件物品的重量为weight[i],价值为value[i]。你的目标是在不超过背包容量的前提下,尽可能多地偷走有价值的物品。每件物品要么完整地拿走(1),要么完全不拿(0),这就是“01”的含义。
底层原理深度剖析
二维数组解法的核心是动态规划。我们创建一个dp[i][j]的二维数组,其中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[n][bagSize]就是我们要求的最大价值。这个过程类似于 Nginx 处理请求,需要考虑到各种情况,比如 Nginx 的 location 匹配规则,选择不同的 upstream 服务器,才能保证服务的稳定和高效。
具体的代码解决方案
下面是使用 C++ 实现的二维数组解法:
#include <iostream>
#include <vector>
using namespace std;
int knapsack(int bagSize, vector<int>& weight, vector<int>& value) {
int n = weight.size();
vector<vector<int>> dp(n + 1, vector<int>(bagSize + 1, 0)); // 初始化dp数组
// 初始化第一列,背包容量为0时,价值都为0
for (int i = 0; i <= n; ++i) {
dp[i][0] = 0;
}
// 遍历物品
for (int i = 1; i <= n; ++i) {
// 遍历背包容量
for (int j = 1; j <= bagSize; ++j) {
if (j < weight[i - 1]) { // 当前背包容量不足以放入第i个物品
dp[i][j] = dp[i - 1][j]; // 不选择第i个物品
} else {
// 选择或不选择第i个物品,取最大值
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
return dp[n][bagSize];
}
int main() {
int bagSize = 4;
vector<int> weight = {2, 1, 3};
vector<int> value = {4, 2, 3};
int max_value = knapsack(bagSize, weight, value);
cout << "最大价值:" << max_value << endl;
return 0;
}
实战避坑经验总结
- 初始化问题: 务必正确初始化
dp数组。dp[0][j]和dp[i][0]通常初始化为0,表示没有物品或背包容量为0时的最大价值。 - 数组下标: 注意
weight和value数组的下标与dp数组的下标之间的对应关系。在状态转移方程中,weight[i-1]和value[i-1]对应于第i个物品的重量和价值。 - 空间复杂度: 二维数组解法的空间复杂度为O(n*bagSize)。在
代码随想录day 35中,也提到了可以通过滚动数组的方式,将空间复杂度优化到O(bagSize)。 - 边界条件: 务必考虑边界条件,例如背包容量为0或没有物品时的情况。
- 数据溢出: 当物品价值很大时,需要注意数据溢出问题,可以选择使用
long long类型。
理解和掌握二维数组解法是解决01背包问题的关键。通过深入理解状态转移方程和注意实战中的避坑经验,我们可以更好地运用动态规划解决实际问题。例如,在高并发场景下,我们可以使用类似的思想来做流量控制,防止系统被瞬间流量冲垮,保证服务的稳定性,这和Nginx的限流策略有异曲同工之妙。
冠军资讯
脱发程序员