From fdec90336d9b9187fecb4050153274b0d75d85a2 Mon Sep 17 00:00:00 2001 From: ITCharge Date: Wed, 15 Mar 2023 16:40:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A2=98=E8=A7=A3?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...23\345\256\266\345\212\253\350\210\215.md" | 42 +++++++------ ...345\256\266\345\212\253\350\210\215 II.md" | 62 +++++++++++-------- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git "a/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" "b/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" index 8b917c8f..d7aef4ca 100644 --- "a/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" +++ "b/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" @@ -5,7 +5,7 @@ ## 题目大意 -**描述**:给定一个数组 `nums`,`nums[i]` 代表第 `i` 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 +**描述**:给定一个数组 $nums$,$nums[i]$ 代表第 $i$ 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 **要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 @@ -44,25 +44,29 @@ ###### 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:代码 @@ -70,19 +74,17 @@ 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:复杂度分析 diff --git "a/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" "b/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" index 54dd7e1c..9a028682 100644 --- "a/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" +++ "b/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" @@ -5,7 +5,7 @@ ## 题目大意 -**描述**:给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 +**描述**:给定一个数组 $nums$,$num[i]$ 代表第 $i$ 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 **要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 @@ -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)」的解题思路。 @@ -42,25 +50,29 @@ ###### 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:动态规划代码 @@ -68,19 +80,17 @@ 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) From e47975e83b42cd8423c53df389d6fc8e6649f6fa Mon Sep 17 00:00:00 2001 From: ITCharge Date: Fri, 17 Mar 2023 18:00:57 +0800 Subject: [PATCH 2/3] Create Pack-ZeroOnePack.py --- .../Pack-ZeroOnePack.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py diff --git a/Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py b/Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py new file mode 100644 index 00000000..62f937ca --- /dev/null +++ b/Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py @@ -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] From d4b4fd1bb7101c7049fe83f4c603aff6186bdae1 Mon Sep 17 00:00:00 2001 From: ITCharge Date: Fri, 17 Mar 2023 18:01:01 +0800 Subject: [PATCH 3/3] Update 01.Knapsack-Problem.md --- .../01.Knapsack-Problem.md | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem.md index 39832ddf..1e1b8e4b 100644 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem.md +++ b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem.md @@ -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/)