一、背包问题概述
背包问题为有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。将这些物品装进背包,怎么装价值最高。 根据条件不同,可以分为以下几类:
- 01背包:每件物品只有一个,要不拿一个,要不不拿
- 完全背包:每件物品有无数个,可以拿0-任意个
- 多重背包:不同物品数量不同
- 分组背包:多种物品组成一个小组,每组只能拿一种
- 组合背包:01背包,完全背包与多重背包的组合体
leetcode上学会01背包,完全背包和多种背包足够了,因此这里只讲这三种。全面的背包问题讲解推荐《背包九讲》
二、01背包二维解法
1、问题概述
有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只有一个,求解将哪些物品装入背包里物品价值总和最大。
2、dp数组定义
定义dp数组为二维数组,dp[i][j]表示01背包条件下物品编码为0-i,背包重量为j时的最大价值总和。
2、递推公式
dp[i][j]可以由两种情况推导而来:
- 在增加物品i时,不将其放入背包:此时
dp[i][j]
由dp[i - 1][j]
推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j] = dp[i - 1][j]
- 在增加物品i时,将其放入背包:此时
dp[i][j]
由dp[i - 1][j - weight[i]]
推出,dp[i - 1][j - weight[i]]
为背包容量为j - weight[i]
的时候不放物品i的最大价值,那么dp[i][j] = dp[i - 1][j - weight[i]] + value[i]
综上,dp[i][j]取两种情况下价值最大的情况:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
3、dp数组初始化
由于dp[i][j]是由dp[i-1][j]推导出来的,因此要初始化全部的dp[0][j]
- 首先将dp数组全部初始化为0
- dp[i][0]全部初始化为0
- 然后初始化只放物品0时候背包的最大价值dp[0][j]
//当前背包容量j大于等于物品0的重量时候,初始化dp[0][j] = value[0]
//这里是倒序遍历dp[0][bagWeight]~dp[0][0]实现上述功能,倒序遍历实现了每样物品只拿一个,当计算dp[0][j]的时候其前面的dp[0][j - weight[0]]是0,没放东西的,这里是为了和更复杂的背包问题初始化保持一致
// 初始化 dp
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
for (int j = bagWeight; j >= weight[0]; j--) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
4、遍历顺序
根据递推公式dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
,dp[i][j]依赖其上方元素和左上方元素,因此先遍历背包(先横向遍历)和先遍历物品(先纵向遍历)都是可以的
5、完整代码
void test_2_wei_bag_problem1() {
vector<int> weight = {
1, 3, 4};
vector<int> value = {
15, 20, 30};
int bagWeight = 4;
/-----dp数组初始化-----/
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
for (int j = bagWeight; j >= weight[0]; j--) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
/----遍历,递推公式---/
for(int i = 1; i < weight.size(); i++) {
// 遍历物品
for(int j = 0; j <= bagWeight; j++) {
// 遍历背包容量
if