class Solution {
public:
int minDistance(string word1, string word2) {
}
};
编辑距离是用来量化两个字符串的相似度的。
所谓编辑距离就是指,将一个字符串转换成另一个字符串,需要的最少编辑操作次数(比如增加一个字符、删除一个字符、替换一个字符)
对于两个完全相同的字符串来说,编辑距离就是0.
根据所包含的编辑,编辑距离有多种不同的计算方式,比较著名的有
比如,两个字符串 mitcmu 和mtacnu 的莱文斯坦距离是 3,最长公共子串长度是 4。
本题要求计算莱文斯坦距离
回溯是一个递归处理的过程:
word1[i]
和word2[j]
匹配,我们递归考察word1[i+1]
和word2[j+1]
。word1[i]
和word2[j]
不匹配,那我们有多种处理方式可选:
word1[i]
,然后递归考察word1[i+1]
和word2[j]
word2[j]
,然后递归考察word1[i]
和word2[j+1]
word1[i]
前面添加一个跟word2[j]
相同的字符,然后递归考察word1[i]
和word2[j+1]
word2[j]
前面添加一个跟word1[i]
相同的字符,然后递归考察word1[i+1]
和word2[j]
word1[i]
替换成word2[j]
,或者将word2[j]
替换成word1[i]
,然后递归考察 word1[i+1]
和word2[j+1]
class Solution {
int miDist = INT32_MAX; //存储结果
void lwstBT(std::string &a, std::string &b, int i, int j, int edist){
if(i == a.size() || j == b.size()){
if(i < a.size()){
edist += (int)(a.size() - i);
}
if(j < b.size()){
edist += (int)(b.size() - j);
}
miDist = std::min(miDist, edist);
return;
}
if(a[i] == b[j]){ // 两个字符匹配
lwstBT(a, b, i + 1, j + 1, edist);
}else{//两个字符不匹配
lwstBT(a, b, i + 1, j , edist + 1); // 删除 a[i] 或者 b[j] 前添加一个字符
lwstBT(a, b, i, j + 1, edist + 1); // 删除 b[j] 或者 a[i] 前添加一个字符
lwstBT(a, b, i + 1, j + 1, edist + 1); // 将 a[i] 和 b[j] 替换为相同字符
}
}
public:
int minDistance(string word1, string word2){
lwstBT(word1, word2, 0,0, 0);
return miDist;
}
};
根据回溯算法的代码实现,我们可以画出递归树,看是否存在重复子问题。如果存在重复子问题,那我们就可以考虑是否能用动态规划来解决;如果不存在重复子问题,那回溯就是最好的解决方法。
在递归树中,每个节点代表一个状态,状态包含三个变量 ( i , j , e d i s t ) (i,j,edist) (i,j,edist),其中,edist表示处理到 a [ i ] a[i] a[i]和 b [ j ] b[j] b[j]时,已经执行的编辑操作的次数。
在递归树中, ( i , j ) (i,j) (i,j)两个变量重复的节点很多,比如 ( 3 , 2 ) (3,2) (3,2)和 ( 2 , 3 ) (2,3) (2,3)。对于 ( i , j ) (i,j) (i,j)相同的节点,我们只需要考虑保留edist最小的,继续递归处理就可以了,剩下的节点都可以舍弃。所以,状态就从 ( i , j , e d i s t ) (i,j,edist) (i,j,edist)变成了 ( i , j , m i n d i s t ) (i,j,mindist) (i,j,mindist),其中mindist表示处理到 a [ i ] a[i] a[i]和 b [ j ] b[j] b[j],已经执行的最少编辑次数。
这里的动态递归跟矩阵最短路径很相似,在矩阵最短路径中,到达状态(i,j)只能通过(i-1,j)或者(i,j-1)两个状态转移过来,而字符编辑距离状态(i,j)可能从(i-1,j),(i,j-1),(i-1,j-1)三个状态中的任意一个转移过来。
样本对应模型往往是根据结尾位置来做可能性划分的
举个例子
(1)初始化表
(2)中间情况怎么决策
class Solution {
private:
int minCost(std::string s1, std::string s2, int ac, int dc, int rc){
int M = s1.size(), N = s2.size() ;
std::vector<std::vector<int>> dp(M + 1, std::vector<int>(N + 1, 0));
dp[0][0] = 0;
for (int i = 1; i <= M; ++i) {
dp[i][0] = dc * i; //非空串--->空串,只能删除字符
}
for (int j = 1; j <= N; ++j) {
dp[0][j] = ac * j; //空串--->非空串,只能添加字符
}
for (int i = 1; i <= M; ++i) {
for (int j = 1; j <= N; ++j) {
if(s1[i - 1] == s2[j - 1]){
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = dp[i - 1][j - 1] + rc;
}
dp[i][j] = std::min(dp[i][j - 1] + dc, dp[i][j]);
dp[i][j] = std::min(dp[i - 1][j] + ac, dp[i][j]);
}
}
return dp[M][N];
}
public:
int minDistance(string word1, string word2){
return minCost(word1, word2, 1,1, 1);
}
};
(1)定义状态:
f[i][j]
表示将word1
的前i
个字符变成word2
的前j
个字符所需要进行的最少操作次数dp[0][j]
和 dp[i][0]
(2)状态转移方程:对于f[i][j]
,考虑word1的第i个字符与word2的第j个字符,分为两种情况:
word1[i] == word2[j]
,则f[i][j] == f[i - 1][j - 1]
word1[i] != word2[j]
,我们有三种选择,替换、删除、插入:
word1
的第i
个字符或者替换word2
的第j
个字符,则f[i][j] == f[i - 1][j - 1] + 1
;word1
的第i
个字符或者删除word2
的第j
个字符,则f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1
;word2[j]
后面添加 word1[i]
或者在word1[i]
后添加word2[j]
,则f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1
;dp[i][j]
时,dp[i - 1][j]
, dp[i][j - 1]
,dp[i - 1][j - 1]
均已经确定了(4)dp如何初始化:从一个字符串变成空字符串,非空字符串的长度就是编辑距离
for (int i = 0; i <= len1; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= len2; j++) {
dp[0][j] = j;
}
(6)空间优化:
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 0; i <= m; ++i) dp[i][0] = i;
for (int i = 0; i <= n; ++i) dp[0][i] = i;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++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 - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
return dp[m][n];
}
};
题目 | 思路 |
---|---|
leetcode:72. 编辑距离 Edit Distance | |
leetcode:1143. 最长公共子序列 Longest Increasing Subsequence | 只关心str1[0…i],str2[0…j],对于它们的最长公共子序列长度是多少(样本对应模型,往往以考虑结尾来组织可能性) |
leetcode:583. 使得两个字符相同的最少删除次数 Delete Operation for Two Strings | |
leetcode:712. 使得两个字符相同时所需删除字符最小ASCII值和 Minimum ASCII Delete Sum for Two Strings |