This commit is contained in:
krahets
2023-08-20 13:37:08 +08:00
parent 2e27ad1680
commit 44a8568356
32 changed files with 140 additions and 130 deletions

View File

@@ -102,7 +102,7 @@ $$
根据以上分析,我们已经可以直接写出动态规划代码。然而子问题分解是一种从顶至底的思想,因此按照“暴力搜索 $\rightarrow$ 记忆化搜索 $\rightarrow$ 动态规划”的顺序实现更加符合思维习惯。
### 方法一:暴力搜索
### 1.   方法一:暴力搜索
从状态 $[i, j]$ 开始搜索,不断分解为更小的状态 $[i-1, j]$ 和 $[i, j-1]$ ,包括以下递归要素:
@@ -326,7 +326,7 @@ $$
每个状态都有向下和向右两种选择,从左上角走到右下角总共需要 $m + n - 2$ 步,所以最差时间复杂度为 $O(2^{m + n})$ 。请注意,这种计算方式未考虑临近网格边界的情况,当到达网络边界时只剩下一种选择。因此实际的路径数量会少一些。
### 方法二:记忆化搜索
### 2.   方法二:记忆化搜索
我们引入一个和网格 `grid` 相同尺寸的记忆列表 `mem` ,用于记录各个子问题的解,并将重叠子问题进行剪枝。
@@ -588,7 +588,7 @@ $$
<p align="center"> 图:记忆化搜索递归树 </p>
### 方法三:动态规划
### 3. &nbsp; 方法三:动态规划
基于迭代实现动态规划解法。
@@ -895,7 +895,7 @@ $$
<p align="center"> 图:最小路径和的动态规划过程 </p>
### 状态压缩
### 4. &nbsp; 状态压缩
由于每个格子只与其左边和上边的格子有关,因此我们可以只用一个单行数组来实现 $dp$ 表。

View File

@@ -29,6 +29,8 @@ status: new
<p align="center"> 图:基于决策树模型表示编辑距离问题 </p>
### 1. &nbsp; 动态规划思路
**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表**
每一轮的决策是对字符串 $s$ 进行一次编辑操作。
@@ -74,7 +76,7 @@ $$
观察状态转移方程,解 $dp[i, j]$ 依赖左方、上方、左上方的解,因此通过两层循环正序遍历整个 $dp$ 表即可。
### 代码实现
### 2. &nbsp; 代码实现
=== "Java"
@@ -413,7 +415,7 @@ $$
<p align="center"> 图:编辑距离的动态规划过程 </p>
### 状态压缩
### 3. &nbsp; 状态压缩
由于 $dp[i,j]$ 是由上方 $dp[i-1, j]$ 、左方 $dp[i, j-1]$ 、左上方状态 $dp[i-1, j-1]$ 转移而来,而正序遍历会丢失左上方 $dp[i-1, j-1]$ ,倒序遍历无法提前构建 $dp[i, j-1]$ ,因此两种遍历顺序都不可取。

View File

@@ -54,7 +54,7 @@ $$
根据以上分析,我们接下来按顺序实现暴力搜索、记忆化搜索、动态规划解法。
### 方法一:暴力搜索
### 1. &nbsp; 方法一:暴力搜索
搜索代码包含以下要素:
@@ -275,7 +275,7 @@ $$
<p align="center"> 图0-1 背包的暴力搜索递归树 </p>
### 方法二:记忆化搜索
### 2. &nbsp; 方法二:记忆化搜索
为了保证重叠子问题只被计算一次,我们借助记忆列表 `mem` 来记录子问题的解,其中 `mem[i][c]` 对应 $dp[i, c]$ 。
@@ -541,7 +541,7 @@ $$
<p align="center"> 图0-1 背包的记忆化搜索递归树 </p>
### 方法三:动态规划
### 3. &nbsp; 方法三:动态规划
动态规划实质上就是在状态转移中填充 $dp$ 表的过程,代码如下所示。
@@ -824,7 +824,7 @@ $$
<p align="center"> 图0-1 背包的动态规划过程 </p>
### 状态压缩
### 4. &nbsp; 状态压缩
由于每个状态都只与其上一行的状态有关,因此我们可以使用两个数组滚动前进,将空间复杂度从 $O(n^2)$ 将低至 $O(n)$ 。

View File

@@ -17,6 +17,8 @@ status: new
<p align="center"> 图:完全背包问题的示例数据 </p>
### 1. &nbsp; 动态规划思路
完全背包和 0-1 背包问题非常相似,**区别仅在于不限制物品的选择次数**。
- 在 0-1 背包中,每个物品只有一个,因此将物品 $i$ 放入背包后,只能从前 $i-1$ 个物品中选择。
@@ -33,7 +35,7 @@ $$
dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
$$
### 代码实现
### 2. &nbsp; 代码实现
对比两道题目的代码,状态转移中有一处从 $i-1$ 变为 $i$ ,其余完全一致。
@@ -270,7 +272,7 @@ $$
}
```
### 状态压缩
### 3. &nbsp; 状态压缩
由于当前状态是从左边和上边的状态转移而来,**因此状态压缩后应该对 $dp$ 表中的每一行采取正序遍历**。
@@ -541,6 +543,8 @@ $$
<p align="center"> 图:零钱兑换问题的示例数据 </p>
### 1. &nbsp; 动态规划思路
**零钱兑换可以看作是完全背包的一种特殊情况**,两者具有以下联系与不同点:
- 两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”。
@@ -570,7 +574,7 @@ $$
当无硬币时,**无法凑出任意 $> 0$ 的目标金额**,即是无效解。为使状态转移方程中的 $\min()$ 函数能够识别并过滤无效解,我们考虑使用 $+ \infty$ 来表示它们,即令首行所有 $dp[0, a]$ 都等于 $+ \infty$ 。
### 代码实现
### 2. &nbsp; 代码实现
大多数编程语言并未提供 $+ \infty$ 变量,只能使用整型 `int` 的最大值来代替。而这又会导致大数越界:状态转移方程中的 $+ 1$ 操作可能发生溢出。
@@ -911,7 +915,7 @@ $$
<p align="center"> 图:零钱兑换问题的动态规划过程 </p>
### 状态压缩
### 3. &nbsp; 状态压缩
零钱兑换的状态压缩的处理方式和完全背包一致。
@@ -1188,6 +1192,8 @@ $$
<p align="center"> 图:零钱兑换问题 II 的示例数据 </p>
### 1. &nbsp; 动态规划思路
相比于上一题,本题目标是组合数量,因此子问题变为:**前 $i$ 种硬币能够凑出金额 $a$ 的组合数量**。而 $dp$ 表仍然是尺寸为 $(n+1) \times (amt + 1)$ 的二维矩阵。
当前状态的组合数量等于不选当前硬币与选当前硬币这两种决策的组合数量之和。状态转移方程为:
@@ -1198,7 +1204,7 @@ $$
当目标金额为 $0$ 时,无需选择任何硬币即可凑出目标金额,因此应将首列所有 $dp[i, 0]$ 都初始化为 $1$ 。当无硬币时,无法凑出任何 $>0$ 的目标金额,因此首行所有 $dp[0, a]$ 都等于 $0$ 。
### 代码实现
### 2. &nbsp; 代码实现
=== "Java"
@@ -1468,7 +1474,7 @@ $$
}
```
### 状态压缩
### 3. &nbsp; 状态压缩
状态压缩处理方式相同,删除硬币维度即可。