LeetCode - 72. Edit Distance
Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.
You have the following 3 operations permitted on a word:
- Insert a character
- Delete a characte
- Replace a character
Input: word1 = “horse”, word2 = “ros”, Output: 3
Explanation:
horse -> rorse (replace ‘h’ with ‘r’)
rorse -> rose (remove ‘r’)
rose -> ros (remove ‘e’)
就是对于两个字符串,只能有三种操作:
- 删除一个字符串中的任意一个字符
- 在一个字符串任意位置添加一个字符
- 修改一个字符串中的任意一个字符
问最少几次操作让两个字符串相同。这个次数就叫编辑距离,比如完全相同的两个字符串编辑距离是 0,{ “abc”、“abd” }、{ “ab”、“abc” } 这样的,编辑距离是 1。
用一个二维 DP 数组记录,DP[i][i] 表示以 A 字符串第 i - 1 位置为结尾,B 字符串第 j - 1 位置为结尾时,编辑距离是多少。初始化很容易理解,就是 dp[0][i] = i,相当于 A 字符串是空的,那么变成 B 就需要 i 次insert,或者 i 次 delete。对于每个 A[i] 和 B[j],有两种情况:
- 当 A[i] == B[j] 时,dp[i + 1][j + 1] 理所当然的等于 dp[i][j],因为不需要操作,
状态转移方程:dp[i + 1][j + 1] = dp[i][j]
. - 当 A[i] != B[j] 时,dp[i + 1][j + 1] 有三种情况:
- 可以在 A[i] 位置插入一个 B[j] 来使得新的 A[i] == B[j],也就是 A[i] 不再和 B[j] 进行比较而是和 B[j - 1] 进行比较,因为 B[j] 用来和新的 A[i] 相同了;或者让 B 删除 B[j],和 A 插入一个新的字符一样,就比如 { “ab”、“abc” } 可以变成 { “abc”、“abc” },或者 { “ab”、“ab” },这两种是一样的。
状态转移方程:dp[i + 1][j + 1] = dp[i + 1][j] + 1
. - 可以在 B[j] 位置插入一个 A[i] 来使得新的 B[j] == A[i],同上,也就是 B[j] 不再和 A[i] 进行比较而是和 A[i - 1] 进行比较。
状态转移方程:dp[i + 1][j + 1] = dp[i][j + 1] + 1
. - 可以通过修改 A[i] 或者 B[j],使得 A[i] == B[j],也就是没有新的字符加入,就是 A 和 B 都用上一个字符的比较作为比较,因为新的被修改成对的了。
状态转移方程:dp[i + 1][j + 1] = dp[i][j] + 1
.
- 可以在 A[i] 位置插入一个 B[j] 来使得新的 A[i] == B[j],也就是 A[i] 不再和 B[j] 进行比较而是和 B[j - 1] 进行比较,因为 B[j] 用来和新的 A[i] 相同了;或者让 B 删除 B[j],和 A 插入一个新的字符一样,就比如 { “ab”、“abc” } 可以变成 { “abc”、“abc” },或者 { “ab”、“ab” },这两种是一样的。
三种方式选一种代价最小的即可。也就是状态转移方程如下:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
−
1
]
,
w
o
r
d
1
[
i
]
=
=
w
o
r
d
2
[
j
]
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
,
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
+
1
,
w
o
r
d
1
[
i
]
!
=
w
o
r
d
2
[
j
]
dp[i][j] = \begin{cases} dp[i - 1][j - 1], \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ word1[i] == word2[j]\\ min\begin{pmatrix} dp[i - 1][j - 1],\\ dp[i][j - 1],\\ dp[i - 1][j] \end{pmatrix} + 1, \ word1[i]\ \ != word2[j] \end{cases}
dp[i][j]=⎩⎪⎪⎨⎪⎪⎧dp[i−1][j−1], word1[i]==word2[j]min⎝⎛dp[i−1][j−1],dp[i][j−1],dp[i−1][j]⎠⎞+1, word1[i] !=word2[j]
用一个例子举例:{ “kitten”、“sitting” },dp 矩阵值如下,第一行、第一列都是其中一个字符串是空的的时候的值,即初始化的值,最后结果就是右下角的值。
看懂了状态转移方式以及上图,代码就很好写了:
int minDistance(string word1, string word2) {
const size_t len1 = word1.length(), len2 = word2.length();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
// 初始化
for(int i = 0; i <= len1; ++i) dp[i][0] = i;
for(int j = 0; j <= len2; ++j) dp[0][j] = j;
// 状态转移计算
for(int i = 1; i <= len1; ++i)
for(int j = 1; j <= len2; ++j) {
if(word1[i - 1] == word2[j - 1])
dp[i][j] = dp[i - 1][j - 1];
else
dp[i][j] = min({ dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] }) + 1;
}
return dp[len1][len2];
}