一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
描述
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家 Levenshtein 提出的,故又叫 Levenshtein Distance 。
例如:
字符串A: abcdefg
字符串B: abcdef
通过增加或是删掉字符 ”g” 的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
要求:
给定任意两个字符串,写出一个算法计算它们的编辑距离。
数据范围:给定的字符串长度满足 1≤len(str)≤1000
输入描述:
每组用例一共2行,为输入的两个字符串
输出描述:
每组用例输出一行,代表字符串的距离
示例1
输入:
abcdefg
abcdef
输出:
1
题目的主要信息:
- Levenshtein 距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数,编辑可包括将一个字符替换成另一个字符,插入一个字符,删除一个字符
- 给定任意两个字符串,计算二者的编辑距离
方法一:动态规划
具体做法:
我们用二维dp数组记录编辑距离,其中dp[i][j]表示字符串s1前i个字符与字符串s2前j个字符之间的编辑距离。
初始状态时,s1中第0个字符与s2中每个字符的距离就是s2每个字符为止的长度,s2中第0个字符与s1中每个字符的距离就是s1每个字符为止的长度,因此二维数组第1行第1列分别置为相应的列号和行号。
后面我们对于字符串s1中从1到n每个长度,都计算相应的到字符串2从1到m每个长度的距离。转移方程为:
dp[i][j]=min{dp[i−1][j−1]+(s1[i]==s2[j]?0:1),min(dp[i−1][j],dp[i][j−1]+1)}
其中dp[i−1][j]表示字符串s1前i−1个字符到字符串s2前j个字符的距离,增加一个字符即可得到,dp[i][j−1]表示字符串s1前i个字符到字符串s2前j−1个字符的距离, 删除一个字符即可得到,因此转移都是加1次操作。dp[i−1][j−1]表示字符串s1前i−1个字符到字符串s2前j−1个字符的距离,如果现在两个字符相同,则不变,否则增加一次修改操作,距离加1.
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
while(cin >> s1 >> s2){
int n = s1.length();
int m = s2.length();
vector<vector<int> > dp(n + 1, vector<int>(m + 1, 0)); //dp[i][j]表示s1中前i个字符与s2中前j个字符的距离
for(int i = 1; i <= m; i++) //s1中第0个字符与s2中每个字符的距离就是s2每个字符为止的长度
dp[0][i] = i;
for(int i = 1; i <= n; i++) //s2中第0个字符与s1中每个字符的距离就是s1每个字符为止的长度
dp[i][0] = i;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
int temp = dp[i - 1][j - 1];
if(s1[i - 1] != s2[j - 1]) //如果前两个字符不同,要多增加1的距离
temp++;
dp[i][j] = min(temp, min(dp[i - 1][j], dp[i][j - 1]) + 1); //状态转移,选出最小
}
}
cout << dp[n][m] << endl;
}
return 0;
}
复杂度分析:
- 时间复杂度:,其中n和m分别为两个字符串的长度,两层遍历
- 空间复杂度:,辅助数组dp的大小
方法二:动态规划空间优化
具体做法:
我们发现方法一在遍历的时候二维数组只使用到了本行i及上一行i−1,因为我们可以使用滚动数组优化(k=1−k,k表示行),每次遍历都只有两行,而下标在不断交替使用。每行首列在外循环进入的时候初始化为行号,原理同上。
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1, s2;
while(cin >> s1 >> s2){
int n = s1.length();
int m = s2.length();
vector<vector<int> > dp(2, vector<int>(m + 1, 0)); //dp[i][j]表示s1中前i个字符与s2中前j个字符的距离,滚动数组表示
for(int i = 1; i <= m; i++) //s1中第0个字符与s2中每个字符的距离就是s2每个字符为止的长度
dp[0][i] = i;
int k = 0;
for(int i = 1; i <= n; i++){
k = 1 - k; //数组交换行
dp[k][0] = i; //初始置为该行号
for(int j = 1; j <= m; j++){
int temp = dp[1 - k][j - 1];
if(s1[i - 1] != s2[j - 1]) //如果这两个字符不同,要多增加1的距离
temp++;
dp[k][j] = min(temp, min(dp[1 - k][j], dp[k][j - 1]) + 1); //状态转移,选出最小
}
}
cout << dp[k][m] << endl;
}
return 0;
}
复杂度分析:
- 时间复杂度:,其中n和m分别为两个字符串的长度,两层遍历
- 空间复杂度:,辅助数组dp只有两行,每行长度m+1