313 01背包最大价值问题 | 豆包MarsCode AI刷题

129 阅读3分钟

问题描述

一个旅行者外出旅行时需要将 n 件物品装入背包,背包的总容量为 m。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。

给定两个整数数组 weightsvalues,其中 weights[i] 表示第 i 个物品的重量,values[i] 表示第 i 个物品的价值。你需要输出在满足背包总容量为 m 的情况下,背包中物品的最大总价值。


测试样例

样例1:

输入:n = 3 ,weights = [2, 1, 3] ,values = [4, 2, 3] ,m = 3
输出:6

样例2:

输入:n = 4 ,weights = [1, 2, 3, 2] ,values = [10, 20, 30, 40] ,m = 5
输出:70

样例3:

输入:n = 2 ,weights = [1, 4] ,values = [5, 10] ,m = 4
输出:10

拿到这道题可以看出来完完全全就是一个经典的01背包问题 并没有任何的修改 所以还是经典的动归五部曲走一遍

第一步:就是找到dp【j】代表什么 对于这道题是要代表:容量是j 的背包 装的物品价值最大可以是dp【j】

第二步:dp数组的递推公式:二维dp数组的递推公式为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);二维的是比较好理解的 但是现在就是想用一维的来做 就是上上一层的拷贝就可以了 所以仅仅去掉i这个维度就可以了 所以写一下现在的递推公式:dp[j]=max(dp[j],dp[j-weight[i]]+values[i]); 可以看出相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。

第三步:初始化的问题 紧扣dp【j】数组的定义 我们发现装的物品价值最大是dp【j】,那么当背包的容量j是0的时候 dp【0】就是0.因为最大价值随着物品增加而增加才对,

第四步:确定遍历的顺序:先物品后背包。二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。

为什么呢?

倒序遍历是为了保证物品i只被放入一次! 。但如果一旦正序遍历了,那么物品0就会被重复加入多次!

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

为什么倒序遍历,就可以保证物品只放入一次呢?

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

那么问题又来了,为什么二维dp数组遍历的时候不用倒序呢?

因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!

贴个可以运行的代码in C++:

#include <iostream>
#include <vector>
using namespace std;

int solution(int n, vector<int> weights, vector<int> values, int m) {
    vector<int>dp(m+1);
    for(int i=0;i<n;i++){
        for(int j=m;j>=weights[i];j--){
            dp[j]=max(dp[j],dp[j-weights[i]]+values[i]);
        }
    }
    return dp[m];

}

int main() {
  cout << (solution(3, {2, 1, 3}, {4, 2, 3}, 3) == 6) << endl;
  cout << (solution(4, {1, 2, 3, 2}, {10, 20, 30, 40}, 5) == 70) << endl;
  cout << (solution(2, {1, 4}, {5, 10}, 4) == 10) << endl;
}