mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-08 21:32:15 +08:00
build
This commit is contained in:
@@ -39,7 +39,7 @@ $$
|
||||
|
||||
那么,上节的爬楼梯题目有没有最优子结构呢?它的目标是求解方案数量,看似是一个计数问题,但如果换一种问法:“求解最大方案数量”。我们意外地发现,**虽然题目修改前后是等价的,但最优子结构浮现出来了**:第 $n$ 阶最大方案数量等于第 $n-1$ 阶和第 $n-2$ 阶最大方案数量之和。所以说,最优子结构的解释方式比较灵活,在不同问题中会有不同的含义。
|
||||
|
||||
根据状态转移方程,以及初始状态 $dp[1] = cost[1]$ , $dp[2] = cost[2]$ ,可以得出动态规划代码。
|
||||
根据状态转移方程,以及初始状态 $dp[1] = cost[1]$ , $dp[2] = cost[2]$ ,我们就可以得到动态规划代码。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -246,6 +246,8 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
下图展示了以上代码的动态规划过程。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图:爬楼梯最小代价的动态规划过程 </p>
|
||||
@@ -443,7 +445,7 @@ $$
|
||||
|
||||
给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,**但不能连续两轮跳 $1$ 阶**,请问有多少种方案可以爬到楼顶。
|
||||
|
||||
例如,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。
|
||||
例如下图,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。
|
||||
|
||||

|
||||
|
||||
@@ -458,7 +460,7 @@ $$
|
||||
- 当 $j$ 等于 $1$ ,即上一轮跳了 $1$ 阶时,这一轮只能选择跳 $2$ 阶。
|
||||
- 当 $j$ 等于 $2$ ,即上一轮跳了 $2$ 阶时,这一轮可选择跳 $1$ 阶或跳 $2$ 阶。
|
||||
|
||||
在该定义下,$dp[i, j]$ 表示状态 $[i, j]$ 对应的方案数。在该定义下的状态转移方程为:
|
||||
如下图所示,在该定义下,$dp[i, j]$ 表示状态 $[i, j]$ 对应的方案数。此时状态转移方程为:
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
|
||||
@@ -40,7 +40,7 @@ status: new
|
||||
|
||||
给定一个 $n \times m$ 的二维网格 `grid` ,网格中的每个单元格包含一个非负整数,表示该单元格的代价。机器人以左上角单元格为起始点,每次只能向下或者向右移动一步,直至到达右下角单元格。请返回从左上角到右下角的最小路径和。
|
||||
|
||||
例如以下示例数据,给定网格的最小路径和为 $13$ 。
|
||||
下图展示了一个例子,给定网格的最小路径和为 $13$ 。
|
||||
|
||||

|
||||
|
||||
@@ -52,7 +52,7 @@ status: new
|
||||
|
||||
状态 $[i, j]$ 对应的子问题为:从起始点 $[0, 0]$ 走到 $[i, j]$ 的最小路径和,解记为 $dp[i, j]$ 。
|
||||
|
||||
至此,我们就得到了一个二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。
|
||||
至此,我们就得到了下图所示的二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。
|
||||
|
||||

|
||||
|
||||
@@ -68,7 +68,7 @@ status: new
|
||||
|
||||
对于状态 $[i, j]$ ,它只能从上边格子 $[i-1, j]$ 和左边格子 $[i, j-1]$ 转移而来。因此最优子结构为:到达 $[i, j]$ 的最小路径和由 $[i, j-1]$ 的最小路径和与 $[i-1, j]$ 的最小路径和,这两者较小的那一个决定。
|
||||
|
||||
根据以上分析,可推出以下状态转移方程:
|
||||
根据以上分析,可推出下图所示的状态转移方程:
|
||||
|
||||
$$
|
||||
dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
|
||||
@@ -88,7 +88,7 @@ $$
|
||||
|
||||
在本题中,处在首行的状态只能向右转移,首列状态只能向下转移,因此首行 $i = 0$ 和首列 $j = 0$ 是边界条件。
|
||||
|
||||
每个格子是由其左方格子和上方格子转移而来,因此我们使用采用循环来遍历矩阵,外循环遍历各行、内循环遍历各列。
|
||||
如下图所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用采用循环来遍历矩阵,外循环遍历各行、内循环遍历各列。
|
||||
|
||||

|
||||
|
||||
@@ -582,7 +582,7 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。
|
||||
如下图所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ status: new
|
||||
|
||||
**第二步:找出最优子结构,进而推导出状态转移方程**
|
||||
|
||||
考虑子问题 $dp[i, j]$ ,其对应的两个字符串的尾部字符为 $s[i-1]$ 和 $t[j-1]$ ,可根据不同编辑操作分为三种情况:
|
||||
考虑子问题 $dp[i, j]$ ,其对应的两个字符串的尾部字符为 $s[i-1]$ 和 $t[j-1]$ ,可根据不同编辑操作分为下图所示的三种情况:
|
||||
|
||||
1. 在 $s[i-1]$ 之后添加 $t[j-1]$ ,则剩余子问题 $dp[i, j-1]$ 。
|
||||
2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。
|
||||
|
||||
@@ -378,7 +378,7 @@ $$
|
||||
dp[i] = dp[i-1] + dp[i-2]
|
||||
$$
|
||||
|
||||
这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。
|
||||
这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。下图展示了该递推关系。
|
||||
|
||||

|
||||
|
||||
@@ -1155,6 +1155,12 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
下图模拟了以上代码的执行过程。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图:爬楼梯的动态规划过程 </p>
|
||||
|
||||
与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的某个特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如,爬楼梯问题的状态定义为当前所在楼梯阶数 $i$ 。
|
||||
|
||||
总结以上,动态规划的常用术语包括:
|
||||
@@ -1163,10 +1169,6 @@ $$
|
||||
- 将最小子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」。
|
||||
- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图:爬楼梯的动态规划过程 </p>
|
||||
|
||||
## 14.1.4 状态压缩
|
||||
|
||||
细心的你可能发现,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无须使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。
|
||||
|
||||
@@ -13,7 +13,7 @@ status: new
|
||||
|
||||
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$ 、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,问在不超过背包容量下能放入物品的最大价值。
|
||||
|
||||
请注意,物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。
|
||||
观察下图,由于物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。
|
||||
|
||||

|
||||
|
||||
@@ -537,6 +537,8 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
下图展示了在记忆化递归中被剪掉的搜索分支。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图:0-1 背包的记忆化搜索递归树 </p>
|
||||
@@ -833,7 +835,7 @@ $$
|
||||
- 如果采取正序遍历,那么遍历到 $dp[i, j]$ 时,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已经被覆盖,此时就无法得到正确的状态转移结果。
|
||||
- 如果采取倒序遍历,则不会发生覆盖问题,状态转移可以正确进行。
|
||||
|
||||
以下动画展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。
|
||||
下图展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
@@ -276,7 +276,7 @@ $$
|
||||
|
||||
由于当前状态是从左边和上边的状态转移而来,**因此状态压缩后应该对 $dp$ 表中的每一行采取正序遍历**。
|
||||
|
||||
这个遍历顺序与 0-1 背包正好相反。请通过以下动画来理解两者的区别。
|
||||
这个遍历顺序与 0-1 背包正好相反。请借助下图来理解两者的区别。
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
Reference in New Issue
Block a user