Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[pull] main from itcharge:main #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
pull merged 3 commits into AlgorithmAndLeetCode:main from itcharge:main
Mar 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,21 +1,92 @@
## 1. 背包问题简介

> **背包问题**:背包问题是线性 DP 问题中一类经典而又特殊的模型。背包问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
### 1.1 背包问题的定义

> **背包问题**:背包问题是线性 DP 问题中一类经典而又特殊的模型。背包问题可以描述为:给定一组物品,每种物品都有自己的重量、价格以及数量。再给定一个最多能装重量为 $W$ 的背包。现在选择一些物品放入背包中,请问在总重量不超过背包装载重量上限的情况下,能装入背包的最大价值总合是多少?

根据物品限制条件的不同,背包问题可分为:0-1 背包问题、完全背包问题、多重背包问题、分组背包问题,以及混合背包问题等。

### 1.2 背包问题的暴力解题思路

背包问题的暴力解题思路比较简单。假设有 $n$ 件物品。我们先枚举出这 $n$ 件物品所有可能的组合。然后再对这些组合判断是否能放入背包,以及是否能得到最大价值。但是这种做法的时间复杂度是 $O(2^n),ドル其中 $n$ 表示物品数量。

暴力解法的时间复杂度是指数级别的,但是我们可以利用动态规划算法减少一下时间复杂度。

## 2. 0-1 背包问题

> **0-1 背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i],ドル价值为 $value[i],ドル每件物品有且只有 1ドル$ 件。请问在总重量不超过背包重量上限的情况下,能装入背包的最大价值是多少?
> **0-1 背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i],ドル价值为 $value[i],ドル每件物品有且只有 1ドル$ 件。请问在总重量不超过背包装载重量上限的情况下,能装入背包的最大价值是多少?

### 2.1 0-1 背包问题基本思路

> **0-1 背包问题的特点**:每种物品有且仅有 1ドル$ 件,可以选择放入背包,也可以选择不放入背包。

#### 思路 1:动态规划

###### 1. 划分阶段

按照物品的序号进行阶段划分。

###### 2. 定义状态

定义状态 $dp[i][w]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。

状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品」,第二维表示「当前背包的装载重量上限」,二维数组值表示「可以获得的最大价值」。

###### 3. 状态转移方程

对于「将前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值 」这个子问题,如果我们只考虑第 $i$ 件物品的放入策略(放入背包和不放入背包两种策略)。则问题可以转换为一个只跟前 $i - 1$ 件物品相关的问题。

1. **如果第 $i$ 件物品不放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w$ 的背包中 ,可以获得的最大价值为 $dp[i - 1][w]$」。
2. **如果第 $i$ 件物品放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w - weight[i]$ 的背包中,可以获得的最大价值为 $dp[i - 1][w - weight[i]]$」」,再加上「放入的第 $i$ 件物品的价值 $value[i]$」,则此时可以获得的最大价值为 $dp[i - 1][w - weight[i]] + value[i]$。

当然第 $i$ 件物品能够放入背包的前提是:当前背包的装载重量上限 ≥ 第 $i$ 件物品的重量,即 $w \ge weight[i]$。

则状态转移方程为:

$dp[i][w] = max \begin{cases} dp[i - 1][w] & 第 i 件物品不放入背包 \cr dp[i - 1][w - weight[i]] + value[i] & 第 i 件物品放入背包 \end{cases}$

###### 4. 初始条件

- 如果背包容量为 0ドル,ドル则无论选取什么物品,可以获得的最大价值一定是 0ドル,ドル即 $dp[i][0] = 0$。
- 前 0ドル$ 件物品所能获得的最大价值一定为 0ドル,ドル即 $dp[0][w] = 0$。

###### 5. 最终结果

#### 思路 1:代码

```Python
class Solution:
def zeroOnePack(self, weight: [int], value: [int], W: int):
size = len(weight)
dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)]

for i in range(1, size + 1):
for w in range(weight[i], W + 1):
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value)

return dp[size]
```

#### 思路 1:复杂度分析

- **时间复杂度**:$O(n \times W),ドル其中 $n$ 为物品数量,$W$ 为背包的装载重量上限。
- **空间复杂度**:$O(n \times W)$。

### 2.2 0-1 背包问题滚动数组优化



## 3. 完全背包问题

> **完全背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 件物品的重量为 $weight[i],ドル价值为 $value[i],ドル每种物品数量没有限制。请问在总重量不超过背包重量上限的情况下,能装入背包的最大价值是多少?
> **完全背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 件物品的重量为 $weight[i],ドル价值为 $value[i],ドル每种物品数量没有限制。请问在总重量不超过背包装载重量上限的情况下,能装入背包的最大价值是多少?

## 4. 多重背包问题

> **多重背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i],ドル价值为 $value[i],ドル件数为 $count[i]$。请问在总重量不超过背包重量上限的情况下,能装入背包的最大价值是多少?
> **多重背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i],ドル价值为 $value[i],ドル件数为 $count[i]$。请问在总重量不超过背包装载重量上限的情况下,能装入背包的最大价值是多少?

## 5. 分组背包问题

## 参考资料

- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack)
- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/)
42 changes: 22 additions & 20 deletions Solutions/0198. 打家劫舍.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## 题目大意

**描述**:给定一个数组 `nums`,`nums[i]` 代表第 `i` 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。
**描述**:给定一个数组 $nums,ドル$nums[i]$ 代表第 $i$ 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。

**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

Expand Down Expand Up @@ -44,45 +44,47 @@

###### 2. 定义状态

定义状态 `dp[i]` 表示为:前 `i` 间房屋所能偷窃到的最高金额。
定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。

###### 3. 状态转移方程

如果房屋数大于等于 `3` 间,则偷窃第 `i` 间房屋的时候,就有两种状态:
$i$ 间房屋的最后一个房子是 $nums[i - 1]$。

- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 `dp[i] = dp[i - 2] + nums[i]`;
- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 `dp[i] = dp[i - 1]`。
如果房屋数大于等于 2ドル$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态:

然后这两种状态取最大值即可,即状态转移方程为:`dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])`,`i > 2` 时。
1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$;
1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$。

然后这两种状态取最大值即可,即状态转移方程为:

$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$

###### 4. 初始条件

- 如果只有一间房,则直接偷这间屋子就能偷到最高金额,即 `dp[0] = nums[i]`
- 如果只有两间房,那么就选择金额最大的那间屋进行偷窃,就可以偷到最高金额,即 `dp[1] = max(nums[0], nums[1])`
- 前 0ドル$ 间房屋所能偷窃到的最高金额为 0ドル,ドル即 $dp[0] = 0$
- 前 1ドル$ 间房屋所能偷窃到的最高金额为 $nums[0],ドル即:$dp[1] = nums[0]$

###### 5. 最终结果

根据我们之前定义的状态,`dp[i]` 表示为:前 `i` 间房屋所能偷窃到的最高金额。则最终结果为 `dp[size - 1]`,`size` 为总的房屋数。
根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。则最终结果为 $dp[size],ドル$size$ 为总的房屋数。

### 思路 1:代码

```Python
class Solution:
def rob(self, nums: List[int]) -> int:
size = len(nums)
if size == 1:
return nums[0]
if size == 2:
return max(nums[0], nums[1])

dp = [0 for _ in range(size)]
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
if size == 0:
return 0

dp = [0 for _ in range(size + 1)]
dp[0] = 0
dp[1] = nums[0]

for i in range(2, size):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
for i in range(2, size + 1):
dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1])

return dp[size - 1]
return dp[size]
```

### 思路 1:复杂度分析
Expand Down
62 changes: 36 additions & 26 deletions Solutions/0213. 打家劫舍 II.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## 题目大意

**描述**:给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。
**描述**:给定一个数组 $nums,ドル$num[i]$ 代表第 $i$ 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。

**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

Expand All @@ -19,20 +19,28 @@
- 示例 1:

```Python
输入 nums = [2,3,2]
输出 3
解释 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
```

- 示例 2:

```Python
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4。
```

## 解题思路

### 思路 1:动态规划

这道题可以看做是「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」的升级版。
这道题可以看做是「[198. 打家劫舍](https://leetcode.cn/problems/house-robber/)」的升级版。

如果房屋数大于等于 `3` 间,偷窃了第 `1` 间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第 `1` 间房屋。
如果房屋数大于等于 3ドル$ 间,偷窃了第 1ドル$ 间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第 1ドル$ 间房屋。

假设总共房屋数量为 `size`,这种情况可以转换为分别求解 `[0, size - 2]``[1, size - 1]` 范围下首尾不相连的房屋所能偷窃的最高金额,然后再取这两种情况下的最大值。而求解 `[0, size - 2]``[1, size - 1]` 范围下首尾不相连的房屋所能偷窃的最高金额问题就跟「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」所求问题一致了。
假设总共房屋数量为 $size$,这种情况可以转换为分别求解 $[0, size - 2]$$[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额,然后再取这两种情况下的最大值。而求解 $[0, size - 2]$$[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额问题就跟「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」所求问题一致了。

这里来复习一下「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」的解题思路。

Expand All @@ -42,45 +50,47 @@

###### 2. 定义状态

定义状态 `dp[i]` 表示为:前 `i` 间房屋所能偷窃到的最高金额。
定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。

###### 3. 状态转移方程

如果房屋数大于等于 `3` 间,则偷窃第 `i` 间房屋的时候,就有两种状态:
$i$ 间房屋的最后一个房子是 $nums[i - 1]$。

如果房屋数大于等于 2ドル$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态:

- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 `dp[i] = dp[i - 2] + nums[i]`;
- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 `dp[i] = dp[i - 1]`
1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$;
1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$

然后这两种状态取最大值即可,即状态转移方程为:`dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])`,`i > 2` 时。
然后这两种状态取最大值即可,即状态转移方程为:

$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$

###### 4. 初始条件

- 如果只有一间房,则直接偷这间屋子就能偷到最高金额,即 `dp[0] = nums[i]`
- 如果只有两间房,那么就选择金额最大的那间屋进行偷窃,就可以偷到最高金额,即 `dp[1] = max(nums[0], nums[1])`
- 前 0ドル$ 间房屋所能偷窃到的最高金额为 0ドル,ドル即 $dp[0] = 0$
- 前 1ドル$ 间房屋所能偷窃到的最高金额为 $nums[0],ドル即:$dp[1] = nums[0]$

###### 5. 最终结果

根据我们之前定义的状态,`dp[i]` 表示为:前 `i` 间房屋所能偷窃到的最高金额。假设求解 `[0, size - 2]``[1, size - 1]` 范围下( `size` 为总的房屋数)首尾不相连的房屋所能偷窃的最高金额问题分别为 `ans1`、`ans2`,则最终结果为 `max(ans1, ans2)`
根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。假设求解 $[0, size - 2]$$[1, size - 1]$ 范围下( $size$ 为总的房屋数)首尾不相连的房屋所能偷窃的最高金额问题分别为 $ans1$、$ans2$,则最终结果为 $max(ans1, ans2)$

### 思路 1:动态规划代码

```Python
class Solution:
def helper(self, nums):
size = len(nums)
if size == 1:
return nums[0]
if size == 2:
return max(nums[0], nums[1])

dp = [0 for _ in range(size)]
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
if size == 0:
return 0

dp = [0 for _ in range(size + 1)]
dp[0] = 0
dp[1] = nums[0]

for i in range(2, size):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
for i in range(2, size + 1):
dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1])

return dp[size - 1]
return dp[size]

def rob(self, nums: List[int]) -> int:
size = len(nums)
Expand Down
20 changes: 20 additions & 0 deletions Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def zeroOnePack(self, weight: [int], value: [int], W: int):
size = len(weight)
dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)]

for i in range(1, size + 1):
for w in range(weight[i], W + 1):
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value)

return dp[size]

def zeroOnePackOptimization(self, weight: [int], value: [int], W: int):
size = len(weight)
dp = [0 for _ in range(W + 1)]

for i in range(1, size + 1):
for w in range(W, weight[i] - 1, -1):
dp[w] = max(dp[w], dp[w - weight[i]] + value[i])

return dp[size]

AltStyle によって変換されたページ (->オリジナル) /