mirror of
https://github.com/krahets/hello-algo.git
synced 2025-12-19 07:17:54 +08:00
Unify punctuation.
This commit is contained in:
@@ -192,8 +192,8 @@ $$
|
||||
|
||||
为此,我们需要扩展状态定义:**状态 $[i, j]$ 表示处在第 $i$ 阶、并且上一轮跳了 $j$ 阶**,其中 $j \in \{1, 2\}$ 。此状态定义有效地区分了上一轮跳了 $1$ 阶还是 $2$ 阶,我们可以据此来决定下一步该怎么跳:
|
||||
|
||||
- 当 $j$ 等于 $1$ ,即上一轮跳了 $1$ 阶时,这一轮只能选择跳 $2$ 阶;
|
||||
- 当 $j$ 等于 $2$ ,即上一轮跳了 $2$ 阶时,这一轮可选择跳 $1$ 阶或跳 $2$ 阶;
|
||||
- 当 $j$ 等于 $1$ ,即上一轮跳了 $1$ 阶时,这一轮只能选择跳 $2$ 阶。
|
||||
- 当 $j$ 等于 $2$ ,即上一轮跳了 $2$ 阶时,这一轮可选择跳 $1$ 阶或跳 $2$ 阶。
|
||||
|
||||
在该定义下,$dp[i, j]$ 表示状态 $[i, j]$ 对应的方案数。在该定义下的状态转移方程为:
|
||||
|
||||
|
||||
@@ -93,10 +93,10 @@ $$
|
||||
|
||||
从状态 $[i, j]$ 开始搜索,不断分解为更小的状态 $[i-1, j]$ 和 $[i, j-1]$ ,包括以下递归要素:
|
||||
|
||||
- **递归参数**:状态 $[i, j]$ ;
|
||||
- **返回值**:从 $[0, 0]$ 到 $[i, j]$ 的最小路径和 $dp[i, j]$ ;
|
||||
- **终止条件**:当 $i = 0$ 且 $j = 0$ 时,返回代价 $grid[0, 0]$ ;
|
||||
- **剪枝**:当 $i < 0$ 时或 $j < 0$ 时索引越界,此时返回代价 $+\infty$ ,代表不可行;
|
||||
- **递归参数**:状态 $[i, j]$ 。
|
||||
- **返回值**:从 $[0, 0]$ 到 $[i, j]$ 的最小路径和 $dp[i, j]$ 。
|
||||
- **终止条件**:当 $i = 0$ 且 $j = 0$ 时,返回代价 $grid[0, 0]$ 。
|
||||
- **剪枝**:当 $i < 0$ 时或 $j < 0$ 时索引越界,此时返回代价 $+\infty$ ,代表不可行。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
我们希望在编辑操作的过程中,问题的规模逐渐缩小,这样才能构建子问题。设字符串 $s$ 和 $t$ 的长度分别为 $n$ 和 $m$ ,我们先考虑两字符串尾部的字符 $s[n-1]$ 和 $t[m-1]$ :
|
||||
|
||||
- 若 $s[n-1]$ 和 $t[m-1]$ 相同,我们可以跳过它们,直接考虑 $s[n-2]$ 和 $t[m-2]$ ;
|
||||
- 若 $s[n-1]$ 和 $t[m-1]$ 不同,我们需要对 $s$ 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题;
|
||||
- 若 $s[n-1]$ 和 $t[m-1]$ 相同,我们可以跳过它们,直接考虑 $s[n-2]$ 和 $t[m-2]$ 。
|
||||
- 若 $s[n-1]$ 和 $t[m-1]$ 不同,我们需要对 $s$ 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题。
|
||||
|
||||
也就是说,我们在字符串 $s$ 中进行的每一轮决策(编辑操作),都会使得 $s$ 和 $t$ 中剩余的待匹配字符发生变化。因此,状态为当前在 $s$ , $t$ 中考虑的第 $i$ , $j$ 个字符,记为 $[i, j]$ 。
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
|
||||
考虑子问题 $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]$ ;
|
||||
3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ ;
|
||||
1. 在 $s[i-1]$ 之后添加 $t[j-1]$ ,则剩余子问题 $dp[i, j-1]$ 。
|
||||
2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。
|
||||
3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ 。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -231,8 +231,8 @@ $$
|
||||
|
||||
为了提升算法效率,**我们希望所有的重叠子问题都只被计算一次**。为此,我们声明一个数组 `mem` 来记录每个子问题的解,并在搜索过程中这样做:
|
||||
|
||||
1. 当首次计算 $dp[i]$ 时,我们将其记录至 `mem[i]` ,以便之后使用;
|
||||
2. 当再次需要计算 $dp[i]$ 时,我们便可直接从 `mem[i]` 中获取结果,从而将重叠子问题剪枝;
|
||||
1. 当首次计算 $dp[i]$ 时,我们将其记录至 `mem[i]` ,以便之后使用。
|
||||
2. 当再次需要计算 $dp[i]$ 时,我们便可直接从 `mem[i]` 中获取结果,从而将重叠子问题剪枝。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -404,9 +404,9 @@ $$
|
||||
|
||||
总结以上,动态规划的常用术语包括:
|
||||
|
||||
- 将数组 `dp` 称为「$dp$ 表」,$dp[i]$ 表示状态 $i$ 对应子问题的解;
|
||||
- 将最小子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」;
|
||||
- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」;
|
||||
- 将数组 `dp` 称为「$dp$ 表」,$dp[i]$ 表示状态 $i$ 对应子问题的解。
|
||||
- 将最小子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」。
|
||||
- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
当我们做出物品 $i$ 的决策后,剩余的是前 $i-1$ 个物品的决策。因此,状态转移分为两种情况:
|
||||
|
||||
- **不放入物品 $i$** :背包容量不变,状态转移至 $[i-1, c]$ ;
|
||||
- **放入物品 $i$** :背包容量减小 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态转移至 $[i-1, c-wgt[i-1]]$ ;
|
||||
- **不放入物品 $i$** :背包容量不变,状态转移至 $[i-1, c]$ 。
|
||||
- **放入物品 $i$** :背包容量减小 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态转移至 $[i-1, c-wgt[i-1]]$ 。
|
||||
|
||||
上述的状态转移向我们揭示了本题的最优子结构:**最大价值 $dp[i, c]$ 等于不放入物品 $i$ 和放入物品 $i$ 两种方案中的价值更大的那一个**。由此可推出状态转移方程:
|
||||
|
||||
@@ -51,10 +51,10 @@ $$
|
||||
|
||||
搜索代码包含以下要素:
|
||||
|
||||
- **递归参数**:状态 $[i, c]$ ;
|
||||
- **返回值**:子问题的解 $dp[i, c]$ ;
|
||||
- **终止条件**:当物品编号越界 $i = 0$ 或背包剩余容量为 $0$ 时,终止递归并返回价值 $0$ ;
|
||||
- **剪枝**:若当前物品重量超出背包剩余容量,则只能不放入背包;
|
||||
- **递归参数**:状态 $[i, c]$ 。
|
||||
- **返回值**:子问题的解 $dp[i, c]$ 。
|
||||
- **终止条件**:当物品编号越界 $i = 0$ 或背包剩余容量为 $0$ 时,终止递归并返回价值 $0$ 。
|
||||
- **剪枝**:若当前物品重量超出背包剩余容量,则只能不放入背包。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
完全背包和 0-1 背包问题非常相似,**区别仅在于不限制物品的选择次数**。
|
||||
|
||||
- 在 0-1 背包中,每个物品只有一个,因此将物品 $i$ 放入背包后,只能从前 $i-1$ 个物品中选择;
|
||||
- 在完全背包中,每个物品有无数个,因此将物品 $i$ 放入背包后,**仍可以从前 $i$ 个物品中选择**;
|
||||
- 在 0-1 背包中,每个物品只有一个,因此将物品 $i$ 放入背包后,只能从前 $i-1$ 个物品中选择。
|
||||
- 在完全背包中,每个物品有无数个,因此将物品 $i$ 放入背包后,**仍可以从前 $i$ 个物品中选择**。
|
||||
|
||||
这就导致了状态转移的变化,对于状态 $[i, c]$ 有:
|
||||
|
||||
- **不放入物品 $i$** :与 0-1 背包相同,转移至 $[i-1, c]$ ;
|
||||
- **放入物品 $i$** :与 0-1 背包不同,转移至 $[i, c-wgt[i-1]]$ ;
|
||||
- **不放入物品 $i$** :与 0-1 背包相同,转移至 $[i-1, c]$ 。
|
||||
- **放入物品 $i$** :与 0-1 背包不同,转移至 $[i, c-wgt[i-1]]$ 。
|
||||
|
||||
从而状态转移方程变为:
|
||||
|
||||
@@ -200,9 +200,9 @@ $$
|
||||
|
||||
**零钱兑换可以看作是完全背包的一种特殊情况**,两者具有以下联系与不同点:
|
||||
|
||||
- 两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”;
|
||||
- 优化目标相反,背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量;
|
||||
- 背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解;
|
||||
- 两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”。
|
||||
- 优化目标相反,背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量。
|
||||
- 背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解。
|
||||
|
||||
**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表**
|
||||
|
||||
@@ -214,8 +214,8 @@ $$
|
||||
|
||||
与完全背包的状态转移方程基本相同,不同点在于:
|
||||
|
||||
- 本题要求最小值,因此需将运算符 $\max()$ 更改为 $\min()$ ;
|
||||
- 优化主体是硬币数量而非商品价值,因此在选中硬币时执行 $+1$ 即可;
|
||||
- 本题要求最小值,因此需将运算符 $\max()$ 更改为 $\min()$ 。
|
||||
- 优化主体是硬币数量而非商品价值,因此在选中硬币时执行 $+1$ 即可。
|
||||
|
||||
$$
|
||||
dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
|
||||
|
||||
Reference in New Issue
Block a user