mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-27 01:16:29 +08:00
993 lines
47 KiB
Markdown
993 lines
47 KiB
Markdown
|
---
|
||
|
comments: true
|
||
|
---
|
||
|
|
||
|
# 14.6 Edit distance problem
|
||
|
|
||
|
Edit distance, also known as Levenshtein distance, refers to the minimum number of modifications required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences.
|
||
|
|
||
|
!!! question
|
||
|
|
||
|
Given two strings $s$ and $t$, return the minimum number of edits required to transform $s$ into $t$.
|
||
|
|
||
|
You can perform three types of edits on a string: insert a character, delete a character, or replace a character with any other character.
|
||
|
|
||
|
As shown in the Figure 14-27 , transforming `kitten` into `sitting` requires 3 edits, including 2 replacements and 1 insertion; transforming `hello` into `algo` requires 3 steps, including 2 replacements and 1 deletion.
|
||
|
|
||
|
![Example data of edit distance](edit_distance_problem.assets/edit_distance_example.png){ class="animation-figure" }
|
||
|
|
||
|
<p align="center"> Figure 14-27 Example data of edit distance </p>
|
||
|
|
||
|
**The edit distance problem can naturally be explained with a decision tree model**. Strings correspond to tree nodes, and a round of decision (an edit operation) corresponds to an edge of the tree.
|
||
|
|
||
|
As shown in the Figure 14-28 , with unrestricted operations, each node can derive many edges, each corresponding to one operation, meaning there are many possible paths to transform `hello` into `algo`.
|
||
|
|
||
|
From the perspective of the decision tree, the goal of this problem is to find the shortest path between the node `hello` and the node `algo`.
|
||
|
|
||
|
![Edit distance problem represented based on decision tree model](edit_distance_problem.assets/edit_distance_decision_tree.png){ class="animation-figure" }
|
||
|
|
||
|
<p align="center"> Figure 14-28 Edit distance problem represented based on decision tree model </p>
|
||
|
|
||
|
### 1. Dynamic programming approach
|
||
|
|
||
|
**Step one: Think about each round of decision, define the state, thus obtaining the $dp$ table**
|
||
|
|
||
|
Each round of decision involves performing one edit operation on string $s$.
|
||
|
|
||
|
We aim to gradually reduce the problem size during the edit process, which enables us to construct subproblems. Let the lengths of strings $s$ and $t$ be $n$ and $m$, respectively. We first consider the tail characters of both strings $s[n-1]$ and $t[m-1]$.
|
||
|
|
||
|
- If $s[n-1]$ and $t[m-1]$ are the same, we can skip them and directly consider $s[n-2]$ and $t[m-2]$.
|
||
|
- If $s[n-1]$ and $t[m-1]$ are different, we need to perform one edit on $s$ (insert, delete, replace) so that the tail characters of the two strings match, allowing us to skip them and consider a smaller-scale problem.
|
||
|
|
||
|
Thus, each round of decision (edit operation) in string $s$ changes the remaining characters in $s$ and $t$ to be matched. Therefore, the state is the $i$-th and $j$-th characters currently considered in $s$ and $t$, denoted as $[i, j]$.
|
||
|
|
||
|
State $[i, j]$ corresponds to the subproblem: **The minimum number of edits required to change the first $i$ characters of $s$ into the first $j$ characters of $t$**.
|
||
|
|
||
|
From this, we obtain a two-dimensional $dp$ table of size $(i+1) \times (j+1)$.
|
||
|
|
||
|
**Step two: Identify the optimal substructure and then derive the state transition equation**
|
||
|
|
||
|
Consider the subproblem $dp[i, j]$, whose corresponding tail characters of the two strings are $s[i-1]$ and $t[j-1]$, which can be divided into three scenarios as shown below.
|
||
|
|
||
|
1. Add $t[j-1]$ after $s[i-1]$, then the remaining subproblem is $dp[i, j-1]$.
|
||
|
2. Delete $s[i-1]$, then the remaining subproblem is $dp[i-1, j]$.
|
||
|
3. Replace $s[i-1]$ with $t[j-1]$, then the remaining subproblem is $dp[i-1, j-1]$.
|
||
|
|
||
|
![State transition of edit distance](edit_distance_problem.assets/edit_distance_state_transfer.png){ class="animation-figure" }
|
||
|
|
||
|
<p align="center"> Figure 14-29 State transition of edit distance </p>
|
||
|
|
||
|
Based on the analysis above, we can determine the optimal substructure: The minimum number of edits for $dp[i, j]$ is the minimum among $dp[i, j-1]$, $dp[i-1, j]$, and $dp[i-1, j-1]$, plus the edit step $1$. The corresponding state transition equation is:
|
||
|
|
||
|
$$
|
||
|
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
|
||
|
$$
|
||
|
|
||
|
Please note, **when $s[i-1]$ and $t[j-1]$ are the same, no edit is required for the current character**, in which case the state transition equation is:
|
||
|
|
||
|
$$
|
||
|
dp[i, j] = dp[i-1, j-1]
|
||
|
$$
|
||
|
|
||
|
**Step three: Determine the boundary conditions and the order of state transitions**
|
||
|
|
||
|
When both strings are empty, the number of edits is $0$, i.e., $dp[0, 0] = 0$. When $s$ is empty but $t$ is not, the minimum number of edits equals the length of $t$, that is, the first row $dp[0, j] = j$. When $s$ is not empty but $t$ is, the minimum number of edits equals the length of $s$, that is, the first column $dp[i, 0] = i$.
|
||
|
|
||
|
Observing the state transition equation, solving $dp[i, j]$ depends on the solutions to the left, above, and upper left, so a double loop can be used to traverse the entire $dp$ table in the correct order.
|
||
|
|
||
|
### 2. Code implementation
|
||
|
|
||
|
=== "Python"
|
||
|
|
||
|
```python title="edit_distance.py"
|
||
|
def edit_distance_dp(s: str, t: str) -> int:
|
||
|
"""编辑距离:动态规划"""
|
||
|
n, m = len(s), len(t)
|
||
|
dp = [[0] * (m + 1) for _ in range(n + 1)]
|
||
|
# 状态转移:首行首列
|
||
|
for i in range(1, n + 1):
|
||
|
dp[i][0] = i
|
||
|
for j in range(1, m + 1):
|
||
|
dp[0][j] = j
|
||
|
# 状态转移:其余行和列
|
||
|
for i in range(1, n + 1):
|
||
|
for j in range(1, m + 1):
|
||
|
if s[i - 1] == t[j - 1]:
|
||
|
# 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1]
|
||
|
else:
|
||
|
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
|
||
|
return dp[n][m]
|
||
|
```
|
||
|
|
||
|
=== "C++"
|
||
|
|
||
|
```cpp title="edit_distance.cpp"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
int editDistanceDP(string s, string t) {
|
||
|
int n = s.length(), m = t.length();
|
||
|
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
|
||
|
// 状态转移:首行首列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Java"
|
||
|
|
||
|
```java title="edit_distance.java"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
int editDistanceDP(String s, String t) {
|
||
|
int n = s.length(), m = t.length();
|
||
|
int[][] dp = new int[n + 1][m + 1];
|
||
|
// 状态转移:首行首列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
if (s.charAt(i - 1) == t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "C#"
|
||
|
|
||
|
```csharp title="edit_distance.cs"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
int EditDistanceDP(string s, string t) {
|
||
|
int n = s.Length, m = t.Length;
|
||
|
int[,] dp = new int[n + 1, m + 1];
|
||
|
// 状态转移:首行首列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
dp[i, 0] = i;
|
||
|
}
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[0, j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i, j] = dp[i - 1, j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n, m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Go"
|
||
|
|
||
|
```go title="edit_distance.go"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
func editDistanceDP(s string, t string) int {
|
||
|
n := len(s)
|
||
|
m := len(t)
|
||
|
dp := make([][]int, n+1)
|
||
|
for i := 0; i <= n; i++ {
|
||
|
dp[i] = make([]int, m+1)
|
||
|
}
|
||
|
// 状态转移:首行首列
|
||
|
for i := 1; i <= n; i++ {
|
||
|
dp[i][0] = i
|
||
|
}
|
||
|
for j := 1; j <= m; j++ {
|
||
|
dp[0][j] = j
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for i := 1; i <= n; i++ {
|
||
|
for j := 1; j <= m; j++ {
|
||
|
if s[i-1] == t[j-1] {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i-1][j-1]
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Swift"
|
||
|
|
||
|
```swift title="edit_distance.swift"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
func editDistanceDP(s: String, t: String) -> Int {
|
||
|
let n = s.utf8CString.count
|
||
|
let m = t.utf8CString.count
|
||
|
var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)
|
||
|
// 状态转移:首行首列
|
||
|
for i in 1 ... n {
|
||
|
dp[i][0] = i
|
||
|
}
|
||
|
for j in 1 ... m {
|
||
|
dp[0][j] = j
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for i in 1 ... n {
|
||
|
for j in 1 ... m {
|
||
|
if s.utf8CString[i - 1] == t.utf8CString[j - 1] {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1]
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "JS"
|
||
|
|
||
|
```javascript title="edit_distance.js"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
function editDistanceDP(s, t) {
|
||
|
const n = s.length,
|
||
|
m = t.length;
|
||
|
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
||
|
// 状态转移:首行首列
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
if (s.charAt(i - 1) === t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] =
|
||
|
Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "TS"
|
||
|
|
||
|
```typescript title="edit_distance.ts"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
function editDistanceDP(s: string, t: string): number {
|
||
|
const n = s.length,
|
||
|
m = t.length;
|
||
|
const dp = Array.from({ length: n + 1 }, () =>
|
||
|
Array.from({ length: m + 1 }, () => 0)
|
||
|
);
|
||
|
// 状态转移:首行首列
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
if (s.charAt(i - 1) === t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] =
|
||
|
Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Dart"
|
||
|
|
||
|
```dart title="edit_distance.dart"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
int editDistanceDP(String s, String t) {
|
||
|
int n = s.length, m = t.length;
|
||
|
List<List<int>> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0));
|
||
|
// 状态转移:首行首列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Rust"
|
||
|
|
||
|
```rust title="edit_distance.rs"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
fn edit_distance_dp(s: &str, t: &str) -> i32 {
|
||
|
let (n, m) = (s.len(), t.len());
|
||
|
let mut dp = vec![vec![0; m + 1]; n + 1];
|
||
|
// 状态转移:首行首列
|
||
|
for i in 1..=n {
|
||
|
dp[i][0] = i as i32;
|
||
|
}
|
||
|
for j in 1..m {
|
||
|
dp[0][j] = j as i32;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for i in 1..=n {
|
||
|
for j in 1..=m {
|
||
|
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] =
|
||
|
std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
dp[n][m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "C"
|
||
|
|
||
|
```c title="edit_distance.c"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
int editDistanceDP(char *s, char *t, int n, int m) {
|
||
|
int **dp = malloc((n + 1) * sizeof(int *));
|
||
|
for (int i = 0; i <= n; i++) {
|
||
|
dp[i] = calloc(m + 1, sizeof(int));
|
||
|
}
|
||
|
// 状态转移:首行首列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
dp[i][0] = i;
|
||
|
}
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[0][j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
int res = dp[n][m];
|
||
|
// 释放内存
|
||
|
for (int i = 0; i <= n; i++) {
|
||
|
free(dp[i]);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Kotlin"
|
||
|
|
||
|
```kotlin title="edit_distance.kt"
|
||
|
/* 编辑距离:动态规划 */
|
||
|
fun editDistanceDP(s: String, t: String): Int {
|
||
|
val n = s.length
|
||
|
val m = t.length
|
||
|
val dp = Array(n + 1) { IntArray(m + 1) }
|
||
|
// 状态转移:首行首列
|
||
|
for (i in 1..n) {
|
||
|
dp[i][0] = i
|
||
|
}
|
||
|
for (j in 1..m) {
|
||
|
dp[0][j] = j
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (i in 1..n) {
|
||
|
for (j in 1..m) {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1]
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Ruby"
|
||
|
|
||
|
```ruby title="edit_distance.rb"
|
||
|
[class]{}-[func]{edit_distance_dp}
|
||
|
```
|
||
|
|
||
|
=== "Zig"
|
||
|
|
||
|
```zig title="edit_distance.zig"
|
||
|
// 编辑距离:动态规划
|
||
|
fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 {
|
||
|
comptime var n = s.len;
|
||
|
comptime var m = t.len;
|
||
|
var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1);
|
||
|
// 状态转移:首行首列
|
||
|
for (1..n + 1) |i| {
|
||
|
dp[i][0] = @intCast(i);
|
||
|
}
|
||
|
for (1..m + 1) |j| {
|
||
|
dp[0][j] = @intCast(j);
|
||
|
}
|
||
|
// 状态转移:其余行和列
|
||
|
for (1..n + 1) |i| {
|
||
|
for (1..m + 1) |j| {
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[i][j] = dp[i - 1][j - 1];
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return dp[n][m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
??? pythontutor "Code Visualization"
|
||
|
|
||
|
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D,%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
|
||
|
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D,%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Full Screen ></a></div>
|
||
|
|
||
|
As shown below, the process of state transition in the edit distance problem is very similar to that in the knapsack problem, which can be seen as filling a two-dimensional grid.
|
||
|
|
||
|
=== "<1>"
|
||
|
![Dynamic programming process of edit distance](edit_distance_problem.assets/edit_distance_dp_step1.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<2>"
|
||
|
![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<3>"
|
||
|
![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<4>"
|
||
|
![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<5>"
|
||
|
![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<6>"
|
||
|
![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<7>"
|
||
|
![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<8>"
|
||
|
![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<9>"
|
||
|
![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<10>"
|
||
|
![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<11>"
|
||
|
![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<12>"
|
||
|
![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<13>"
|
||
|
![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<14>"
|
||
|
![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png){ class="animation-figure" }
|
||
|
|
||
|
=== "<15>"
|
||
|
![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png){ class="animation-figure" }
|
||
|
|
||
|
<p align="center"> Figure 14-30 Dynamic programming process of edit distance </p>
|
||
|
|
||
|
### 3. Space optimization
|
||
|
|
||
|
Since $dp[i, j]$ is derived from the solutions above $dp[i-1, j]$, to the left $dp[i, j-1]$, and to the upper left $dp[i-1, j-1]$, and direct traversal will lose the upper left solution $dp[i-1, j-1]$, and reverse traversal cannot build $dp[i, j-1]$ in advance, therefore, both traversal orders are not feasible.
|
||
|
|
||
|
For this reason, we can use a variable `leftup` to temporarily store the solution from the upper left $dp[i-1, j-1]$, thus only needing to consider the solutions to the left and above. This situation is similar to the unbounded knapsack problem, allowing for direct traversal. The code is as follows:
|
||
|
|
||
|
=== "Python"
|
||
|
|
||
|
```python title="edit_distance.py"
|
||
|
def edit_distance_dp_comp(s: str, t: str) -> int:
|
||
|
"""编辑距离:空间优化后的动态规划"""
|
||
|
n, m = len(s), len(t)
|
||
|
dp = [0] * (m + 1)
|
||
|
# 状态转移:首行
|
||
|
for j in range(1, m + 1):
|
||
|
dp[j] = j
|
||
|
# 状态转移:其余行
|
||
|
for i in range(1, n + 1):
|
||
|
# 状态转移:首列
|
||
|
leftup = dp[0] # 暂存 dp[i-1, j-1]
|
||
|
dp[0] += 1
|
||
|
# 状态转移:其余列
|
||
|
for j in range(1, m + 1):
|
||
|
temp = dp[j]
|
||
|
if s[i - 1] == t[j - 1]:
|
||
|
# 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup
|
||
|
else:
|
||
|
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = min(dp[j - 1], dp[j], leftup) + 1
|
||
|
leftup = temp # 更新为下一轮的 dp[i-1, j-1]
|
||
|
return dp[m]
|
||
|
```
|
||
|
|
||
|
=== "C++"
|
||
|
|
||
|
```cpp title="edit_distance.cpp"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
int editDistanceDPComp(string s, string t) {
|
||
|
int n = s.length(), m = t.length();
|
||
|
vector<int> dp(m + 1, 0);
|
||
|
// 状态转移:首行
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
int leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
int temp = dp[j];
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Java"
|
||
|
|
||
|
```java title="edit_distance.java"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
int editDistanceDPComp(String s, String t) {
|
||
|
int n = s.length(), m = t.length();
|
||
|
int[] dp = new int[m + 1];
|
||
|
// 状态转移:首行
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
int leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
int temp = dp[j];
|
||
|
if (s.charAt(i - 1) == t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "C#"
|
||
|
|
||
|
```csharp title="edit_distance.cs"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
int EditDistanceDPComp(string s, string t) {
|
||
|
int n = s.Length, m = t.Length;
|
||
|
int[] dp = new int[m + 1];
|
||
|
// 状态转移:首行
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
int leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
int temp = dp[j];
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Go"
|
||
|
|
||
|
```go title="edit_distance.go"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
func editDistanceDPComp(s string, t string) int {
|
||
|
n := len(s)
|
||
|
m := len(t)
|
||
|
dp := make([]int, m+1)
|
||
|
// 状态转移:首行
|
||
|
for j := 1; j <= m; j++ {
|
||
|
dp[j] = j
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for i := 1; i <= n; i++ {
|
||
|
// 状态转移:首列
|
||
|
leftUp := dp[0] // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i
|
||
|
// 状态转移:其余列
|
||
|
for j := 1; j <= m; j++ {
|
||
|
temp := dp[j]
|
||
|
if s[i-1] == t[j-1] {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftUp
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1
|
||
|
}
|
||
|
leftUp = temp // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Swift"
|
||
|
|
||
|
```swift title="edit_distance.swift"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
func editDistanceDPComp(s: String, t: String) -> Int {
|
||
|
let n = s.utf8CString.count
|
||
|
let m = t.utf8CString.count
|
||
|
var dp = Array(repeating: 0, count: m + 1)
|
||
|
// 状态转移:首行
|
||
|
for j in 1 ... m {
|
||
|
dp[j] = j
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for i in 1 ... n {
|
||
|
// 状态转移:首列
|
||
|
var leftup = dp[0] // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i
|
||
|
// 状态转移:其余列
|
||
|
for j in 1 ... m {
|
||
|
let temp = dp[j]
|
||
|
if s.utf8CString[i - 1] == t.utf8CString[j - 1] {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1
|
||
|
}
|
||
|
leftup = temp // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "JS"
|
||
|
|
||
|
```javascript title="edit_distance.js"
|
||
|
/* 编辑距离:状态压缩后的动态规划 */
|
||
|
function editDistanceDPComp(s, t) {
|
||
|
const n = s.length,
|
||
|
m = t.length;
|
||
|
const dp = new Array(m + 1).fill(0);
|
||
|
// 状态转移:首行
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
let leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
const temp = dp[j];
|
||
|
if (s.charAt(i - 1) === t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "TS"
|
||
|
|
||
|
```typescript title="edit_distance.ts"
|
||
|
/* 编辑距离:状态压缩后的动态规划 */
|
||
|
function editDistanceDPComp(s: string, t: string): number {
|
||
|
const n = s.length,
|
||
|
m = t.length;
|
||
|
const dp = new Array(m + 1).fill(0);
|
||
|
// 状态转移:首行
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (let i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
let leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (let j = 1; j <= m; j++) {
|
||
|
const temp = dp[j];
|
||
|
if (s.charAt(i - 1) === t.charAt(j - 1)) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Dart"
|
||
|
|
||
|
```dart title="edit_distance.dart"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
int editDistanceDPComp(String s, String t) {
|
||
|
int n = s.length, m = t.length;
|
||
|
List<int> dp = List.filled(m + 1, 0);
|
||
|
// 状态转移:首行
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
int leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
int temp = dp[j];
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Rust"
|
||
|
|
||
|
```rust title="edit_distance.rs"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
fn edit_distance_dp_comp(s: &str, t: &str) -> i32 {
|
||
|
let (n, m) = (s.len(), t.len());
|
||
|
let mut dp = vec![0; m + 1];
|
||
|
// 状态转移:首行
|
||
|
for j in 1..m {
|
||
|
dp[j] = j as i32;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for i in 1..=n {
|
||
|
// 状态转移:首列
|
||
|
let mut leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i as i32;
|
||
|
// 状态转移:其余列
|
||
|
for j in 1..=m {
|
||
|
let temp = dp[j];
|
||
|
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
dp[m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "C"
|
||
|
|
||
|
```c title="edit_distance.c"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
int editDistanceDPComp(char *s, char *t, int n, int m) {
|
||
|
int *dp = calloc(m + 1, sizeof(int));
|
||
|
// 状态转移:首行
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
dp[j] = j;
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (int i = 1; i <= n; i++) {
|
||
|
// 状态转移:首列
|
||
|
int leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i;
|
||
|
// 状态转移:其余列
|
||
|
for (int j = 1; j <= m; j++) {
|
||
|
int temp = dp[j];
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
int res = dp[m];
|
||
|
// 释放内存
|
||
|
free(dp);
|
||
|
return res;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Kotlin"
|
||
|
|
||
|
```kotlin title="edit_distance.kt"
|
||
|
/* 编辑距离:空间优化后的动态规划 */
|
||
|
fun editDistanceDPComp(s: String, t: String): Int {
|
||
|
val n = s.length
|
||
|
val m = t.length
|
||
|
val dp = IntArray(m + 1)
|
||
|
// 状态转移:首行
|
||
|
for (j in 1..m) {
|
||
|
dp[j] = j
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (i in 1..n) {
|
||
|
// 状态转移:首列
|
||
|
var leftup = dp[0] // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = i
|
||
|
// 状态转移:其余列
|
||
|
for (j in 1..m) {
|
||
|
val temp = dp[j]
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1
|
||
|
}
|
||
|
leftup = temp // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
=== "Ruby"
|
||
|
|
||
|
```ruby title="edit_distance.rb"
|
||
|
[class]{}-[func]{edit_distance_dp_comp}
|
||
|
```
|
||
|
|
||
|
=== "Zig"
|
||
|
|
||
|
```zig title="edit_distance.zig"
|
||
|
// 编辑距离:空间优化后的动态规划
|
||
|
fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 {
|
||
|
comptime var n = s.len;
|
||
|
comptime var m = t.len;
|
||
|
var dp = [_]i32{0} ** (m + 1);
|
||
|
// 状态转移:首行
|
||
|
for (1..m + 1) |j| {
|
||
|
dp[j] = @intCast(j);
|
||
|
}
|
||
|
// 状态转移:其余行
|
||
|
for (1..n + 1) |i| {
|
||
|
// 状态转移:首列
|
||
|
var leftup = dp[0]; // 暂存 dp[i-1, j-1]
|
||
|
dp[0] = @intCast(i);
|
||
|
// 状态转移:其余列
|
||
|
for (1..m + 1) |j| {
|
||
|
var temp = dp[j];
|
||
|
if (s[i - 1] == t[j - 1]) {
|
||
|
// 若两字符相等,则直接跳过此两字符
|
||
|
dp[j] = leftup;
|
||
|
} else {
|
||
|
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||
|
dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1;
|
||
|
}
|
||
|
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
|
||
|
}
|
||
|
}
|
||
|
return dp[m];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
??? pythontutor "Code Visualization"
|
||
|
|
||
|
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp_comp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%82%E5%AD%98%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D,%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E4%B8%BA%E4%B8%8B%E4%B8%80%E8%BD%AE%E7%9A%84%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
|
||
|
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp_comp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%82%E5%AD%98%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D,%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E4%B8%BA%E4%B8%8B%E4%B8%80%E8%BD%AE%E7%9A%84%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Full Screen ></a></div>
|