# Knapsack

## Knapsack Problems 背包问题

主要来源：[九章：动态规划之背包问题专题教程](https://www.jiuzhang.com/tutorial/backpack/91)

### 什么是背包问题？

> 简单来讲，你有一个背包，它的容量为V，你同时有若干个物品，它们有各自的体积和价值，将哪些物品装入背包可以使得它们的总体积和不超过背包的容量而总价值和最大？
>
> 我们把形如这样的一类与背包有关的问题称为背包问题。

### 有哪些背包问题？

1. 0 -1 背包问题 [Backpack](http://www.lintcode.com/zh-cn/problem/backpack/), [Backpack II](http://www.lintcode.com/zh-cn/problem/backpack-ii/), [Backpack V](http://www.lintcode.com/zh-cn/problem/backpack-v/), [Backpack VI](http://www.lintcode.com/zh-cn/problem/backpack-vi/), [Backpack IX](http://www.lintcode.com/zh-cn/problem/backpack-ix/)
2. 多重背包问题 [Backpack VII](http://www.lintcode.com/zh-cn/problem/backpack-vii/), [Backpack VIII](http://www.lintcode.com/zh-cn/problem/backpack-viii/)
3. 完全背包问题 [Backpack III](http://www.lintcode.com/zh-cn/problem/backpack-iii/), [Backpack IV](http://www.lintcode.com/zh-cn/problem/backpack-iv/), [Backpack X](http://www.lintcode.com/zh-cn/problem/backpack-x/)

## 0 -1 背包问题

#### 问题概述

> 有 `N` 种物品，一个容量为 `V` 的背包，第 `i` 件物品的体积为 `cap[i]`，价值为 `val[i]`，求在不超过背包容量限制的情况下所能获得的最大物品价值和为多少？

一件物品要么不选，要么选，正好对应于 0-1 两个状态，所以我们一般把形如这样的背包问题称作 **0-1 背包问题**。

#### 问题分析

对于 0-1 背包问题，我们一般选择利用动态规划进行求解

**状态：**

`backpack[i][j]` - 代表在前`i`件物品中选择若干件，这若干件物品的体积和不超过`j`的情况下，所能获得的最大物品价值和

**状态转移方程：**

`backpack[i][j] = max(backpack[i−1][j], backpack[i−1][j − cap[i]] + val[i])`

* 如果不将第 i 件物品放入背包，那么 `backpack[i][j]` 的值为 `backpack[i - 1][j]`；如果将第 i 件物品放入背包，那么 `backpack[i][j] = backpack[i - 1][j - cap[i]] + val[i]`。我们根据 `backpack[i - 1][j]` 和 `backpack[i - 1][j - cap[i]] + val[i]` 的大小选择放还是不放第 i 件物品。

`backpack[0][j]=0, j∈[0,V]`

* 在前 0 件物品中选择若干件，也就是一件都不选，此时无论物品的体积和为何值，物品价值和均为 0。

**最后答案**

为`backpack[N][V]`，即在前 `N` 件物品中选择若干件，这若干件物品的体积和不超过 `V` 的情况下，所能获得的最大物品价值和。

**代码实现**

```java
for (int i = 1; i <= N; ++i) {
    for (int j = 0; j <= V; ++j) {
        backpack[i][j] = backpack[i - 1][j];
        if (j >= cap[i]) {
            backpack[i][j] = Math.max(backpack[i][j], backpack[i - 1][j - cap[i]] + val[i]);
        }
    }
}
```

**复杂度分析**

时间复杂度 O(NV)，空间复杂度 O(NV)。

#### 滚动数组优化

观察到在计算 `backpack[i][j]` 时，我们只用到了`backpack[i - 1][j]` 和 `backpack[i - 1][j - cap[i]` 这两个数，并没有用到 `backpack[i - 1][], backpack[i - 2][], ...` 的信息，也就是 `backpack[i][]` 只和 `backpack[i - 1][]` 有关，那么我们只需要两个数组就够了。

```java
for (int i = 1; i <= N; ++i) {
    for (int j = 0; j <= V; ++j) {
        int index = i & 1;
        backpack[index][j] = backpack[index ^ 1][j];
        if (j >= cap[i]) {
            backpack[index][j] = Math.max(backpack[index][j], backpack[index ^ 1][j - cap[i]] + val[i]);
        }
    }
}
```

空间复杂度从 O(NV) 降到了 O(V)。需要`backpack[2][V]`二维数组

#### 一维数组优化

在第`i`层循环初`f[j]`存的相当于`backpack[i - 1][j]`的值。

在更新`f[j]`时，我们用到了`f[j - cap[i]]`，由于第二层循环倒序，所以`f[j−cap[i]]`未被更新，此时它代表`backpack[i - 1][j - cap[i]]`。所以`f[j] = Math.max(f[j], f[j - cap[i]] + val[i])`等价于`backpack[i][j] = Math.max(backpack[i - 1][j], backpack[i - 1][j - cap[i]] + val[i])`。

在第`i`层循环末`f[j]`存的相当于`backpack[i][j]`的值。

```java
for (int i = 1; i <= N; ++i) {
    for (int j = V; j >= cap[i]; --j) {
        f[j] = Math.max(f[j], f[j - cap[i]] + val[i]);
    }
}
```

## 多重背包问题

#### 问题概述

> 有 `N` 种物品，一个容量为 `V` 的背包，第 `i` 件物品的数量为 `num[i]`，体积为 `cap[i]`，价值为 `val[i]`，求在不超过背包容量限制的情况下所能获得的最大物品价值和为多少？

这个问题与 **0-1 背包**的区别在于，**0-1 背包**中每种物品**有且只有一个**，而**多重背包**中一种物品**有**`nums[i]`**个**。我们一般把形如这样的背包问题称作**多重背包问题**。

#### 问题分析

求解这个问题一个简单的思路就是可以把它看成一个拥有 `∑ num[i]`件物品的 **0-1 背包问题**。

Core Code

```java
for (int i = 1; i <= N; ++i) {
    for (int j = 0; j <= V; ++j) {
        backpack[i][j] = backpack[i - 1][j];
        for (int k = 1; k <= num[i]; ++k) {
            if (j >= k * cap[i]) {
                backpack[i][j] = Math.max(backpack[i][j], backpack[i - 1][j - k * cap[i]] + k * val[i]);
            }
        }
    }
}
```

时间复杂度 `O(V * ∑num[i])`，空间复杂度`O(N*V)`。

**一维数组优化：**

```java
for (int i = 1; i <= N; ++i) {
    for (int k = 1; k <= num[i]; ++k) {
        for (int j = V; j >= cap[i]; --j) {
            f[j] = Math.max(f[j], f[j - cap[i]] + val[i]);
        }
    }
}
```

时间复杂度 `O(V * ∑num[i])`，空间复杂度`O(V)`。

## 完全背包问题

#### 问题概述

> 有 `N` 种物品，一个容量为 `V` 的背包，每种物品都有无限件可用。第 `i` 种物品的体积为 `cap[i]`，价值为 `val[i]`。求在不超过背包的容量限制的情况下所能获得的最大总价值和为多少？

这个问题与 **0-1 背包**和**多重背包**都不同，前者每种物品只有`1`件，后者每种物品有`num[i]`件。而对于**完全背包**来讲，每种物品有**无限件**。

我们一般把形如这样的背包问题称作**完全背包问题**。

#### 问题分析

**问题转化**：虽然题目声称每种物品都有**无限件**，但实际上每种物品最多有 `V / cap[i]` 件，因此这个问题相当于一个 `num[i] = V / cap[i]` 的**多重背包问题**。

Core Code

```java
for (int i = 1; i <= N; ++i) {
    for (int k = 1; k * cap[i] <= V; ++k) {
        for (int j = V; j >= cap[i]; --j) {
            f[j] = Math.max(f[j], f[j - cap[i]] + val[i]);
        }
    }
}
```

例子：

> 给定 n 种具有大小 A\[i] 和价值 V\[i] 的物品（每个物品可以取用无限次）和一个大小为 m 的一个背包, 你可以放入背包里的最大价值是多少? 注意事项：你不能将物品分成小块, 选择的项目的总大小应小于或等于 m。

```java
public class Solution {
    public int backPackIII(int[] A, int[] V, int m) {
        int n = A.length;
        int[] f = new int[m + 1];
        for (int i = 0; i < n; ++i) {
            for (int k = 1; k * A[i] <= m; ++k) {
                for (int j = m; j >= A[i]; --j) {
                    f[j] = Math.max(f[j], f[j - A[i]] + V[i]);
                }
            }
        }
        return f[m];
    }
}
```

时间复杂度 `O(m∑ A[i] )`，空间复杂度 `O(m)`。

#### 时间复杂度优化

而且优化的方法十分简单，我们只需要将 j 的循环由逆向转变为正向即可。

由于同一种物品的个数无限，所以我们可以在任意容量 `j`的背包尝试装入当前物品，`j`从小向大枚举可以保证所有包含第 `i`种物品，体积不超过 `j - A[i]` 的状态被枚举到。

```java
public class Solution {
    public int backPackIII(int[] A, int[] V, int m) {
        int n = A.length;
        int[] f = new int[m + 1];
        for (int i = 0; i < n; ++i) {
            for (int j = A[i]; j <= m; ++j) {
                f[j] = Math.max(f[j], f[j - A[i]] + V[i]);
            }
        }
        return f[m];
    }
}
```

时间复杂度为 O(m)，空间复杂度为 O(m)

### Reference

* [背包问题九讲 2.0 by Tianyi Cui](https://github.com/tianyicui/pack/blob/master/V2.pdf)
* [mnmunknown 背包问题](https://mnmunknown.gitbooks.io/algorithm-notes/82ff0c_bei_bao_wen_ti.html)
* [九章：动态规划之背包问题专题教程](https://www.jiuzhang.com/tutorial/backpack/91)
* [维基百科-背包问题](https://en.wikipedia.org/wiki/Knapsack_problem)
* [背包问题详解：01背包、完全背包、多重背包](https://blog.csdn.net/na_beginning/article/details/62884939)
* [SegmentFault: \[LintCode\] Backpack I II III IV V VI \[背包六问\]](https://segmentfault.com/a/1190000006325321)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://aaronice.gitbook.io/lintcode/knapsack_problems.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
