问题描述
一个旅行者外出旅行时需要将 n 件物品装入背包,背包的总容量为 m。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。
给定两个整数数组 weights 和 values,其中 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;
}