### 0-1 背包问题
01背包是非常经典的算法,很多场景可以抽象成问题模型,这个问题的经典解法是动态规划,不过,还有一种简单但是没那么高效的解法,就是回溯算法 + 记忆化递归。
### 问题描述
我们有一个背包,背包总的承载重量是 bw(backpack weight) kg,现在我们有 n 个物品,每个物品的重量不等,并且**不可分割**,我们现在期望选择几件物品,装在到背包中。在不超过背包所能装载重量的前提下,如何让背包的总重量最大?
注意:这里物品是不可分割的,要么装要么不装,所以叫 0 - 1 背包。
### 回溯 + 记忆化递归
对于每个物品来说,都有两个选择,装进背包或者不装进背包,那么 n 个物品,总的装法就有 2ⁿ 种,去掉总重量超过 bw kg 的,从剩下的装法中选择总重量嘴接近 bw kg 的。不过我们如何才能不重复的穷举出这 2ⁿ 种装法呢?
我们可以把物品依次排列,整个问题就分解成了 n 个阶段,每个阶段对应一个物品怎么选择,先对第一个物品进行处理,选择装进去或者不装进去,然后再递归的处理剩下的物品。
这里还稍微用了一点搜索剪枝的技巧,就是当发现已经选择的物品的重量超过 bw kg 的时候,停止继续下坠剩下的物品。
从上图递归树中可以发现,有些子问题重复,于是再加上记忆化递归,记录已经计算好的 f(i,curLoadSum),下次再计算的时候直接取出,不用继续下坠。
```java
/**
*
* 解法1 回溯 + 记忆化递归
*
* @param i: 考察到哪个物品
* @param curLoadSum: 当前已经装进去的物品的重量和
* @param n:物品个数
* @param bw:背包可承受重量
*/
private void f(int i, int curLoadSum, int n, int bpWeight) {
if (curLoadSum == bw || i == n) {//装满了或者物品全部考察完
//if (curLoadSum > maxW) maxW = curLoadSum;
maxW = Math.max(maxW, curLoadSum);
return;
}
if (mem[i][curLoadSum]) return;
mem[i][curLoadSum] = true;//记忆化递归存储
f(i + 1, curLoadSum, n, bpWeight);//不装第 i 件物品
if (curLoadSum + weights[i] <= bw)//若加上 i 件物品,小于背包承载重量,再继续装 (剪枝)
f(i + 1, curLoadSum + weights[i], n, bpWeight);//装第 i 件物品
}
```
### 动态规划
我们将整个求解过程分为 n 个阶段,每个阶段会决策一个物品是否放到背包中,每个物品决策完之后,背包中的物品的重量就会有多种情况,也就是说会达到多种不同状态。
用一个二维数组 s[n] [w + 1],来记录每层可以达到的不同状态。第 0 个物品的重量是 2,要么装入背包,要么不装入背包,决策完会对应背包中的两种状态,分别用 s[0] [0] 和 s[0] [2] 来表示。
第一个物品的重量也是 2,基于第一件物品装完的状态,第二件物品装完之后会有 4 中状态,分别是:
- 0 不装 -1 不装:s[1] [0]
- 0 不装 -1 装:s[1] [2]
- 0 装 -1 不装:s[1] [2]
- 0 装 -1 装:s[1] [4]
去重之后就是 3 中状态:s[1] [0] 、s[1] [2] 、s[1] [4]
以此类推,直到考察完所有物品。在最后一层找到一个值为 true 并且最接近 9 的值,就是背包中物品总重量的最大值。
```java
/**
*
* 解法2 动态规划
*
* @param n:物品个数
* @param bw: 当前已经装进去的物品的重量和
* @return 最大可以装多少重量
*/
private int dpBackpack (int n, int bw) {
boolean[][] s = new boolean[n][bw + 1];//初始化二维数组
s[0][0] = true;//第一件物品不装
if (weights[0] <= bw) s[0][weights[0]] = true;//第一件物品装
for (int i = 1; i < n; i++) {
for (int j = 0; j <= bw ; j++) {//选择不装 i 件物品
if (s[i - 1][j]) s[i][j] = s[i - 1][j];
}
for (int j = 0; j <= bw - weights[i]; j++) {//选择装 i 件物品
if (s[i - 1][j]) s[i][j + weights[i]] = true;
}
}
for (int i = bw; i >= 0; i--) {//最后一层找到最大值
if (s[n - 1][i]) return i;
}
return 0;
}
```
代码更详细部分请点击: [01背包代码答案](https://github.com/gaoshengnan/LeetCode/blob/master/src/main/java/theoreticalBasis/%E8%83%8C%E5%8C%85/Backpack01.java)