01背包问题
由于leetcode上没原题,故参考卡哥意见自己编题记录一下。
一、题干
背包最大重量为4。物品为:
物品名称 | 重量 | 价值 |
---|---|---|
0 | 1 | 15 |
1 | 3 | 20 |
2 | 4 | 30 |
– | – | – |
问背包能背的物品最大价值是多少?
二、解法
二维dp:
递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagweight = 4;
// 二维数组
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// 初始化
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
test_2_wei_bag_problem1();
}
一维dp:
递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
void test_1_wei_bag_problem() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
// 初始化
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
test_1_wei_bag_problem();
}
三、问答题
-
为什么二维dp两个for循环的嵌套顺序这么写?反过来写行不行?
答:反过来可以,原因是递推公式里本层遍历输入都在本层的左上角。 -
讲一讲初始化的逻辑。
答:二维背包中,dp[i][0],无论是选取哪些物品,背包价值总和一定为0;而dp[0][i], 当 i >= weight[0] 时 = value[0],否则为0; 其他无所谓,都会被覆盖。
一维背包中,dp[0] = 0, 如果如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。 -
一维数组的01背包,两个for循环的顺序反过来写行不行?为什么?
答:不可以。因为一维dp的写法,背包容量一定是要倒序遍历(倒序遍历是为了保证物品i只被放入一次!),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。(原因是,dp数组初始化是0,在先遍历背包容量时,由于从后向前遍历,dp[j - weight[i]] = 0, 没用上上一个状态。)
倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。