题目的主要信息:

  • Levenshtein 距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数,编辑可包括将一个字符替换成另一个字符,插入一个字符,删除一个字符
  • 给定任意两个字符串,计算二者的编辑距离

方法一:动态规划

具体做法:

我们用二维dp数组记录编辑距离,其中dp[i][j]dp[i][j]表示字符串s1前ii个字符与字符串s2前jj个字符之间的编辑距离。

初始状态时,s1中第0个字符与s2中每个字符的距离就是s2每个字符为止的长度,s2中第0个字符与s1中每个字符的距离就是s1每个字符为止的长度,因此二维数组第1行第1列分别置为相应的列号和行号。

后面我们对于字符串s1中从1到nn每个长度,都计算相应的到字符串2从1到mm每个长度的距离。转移方程为:

dp[i][j]=min{dp[i1][j1]+(s1[i]==s2[j]?0:1),min(dp[i1][j],dp[i][j1]+1)}dp[i][j] = min \{ dp[i-1][j-1]+(s1[i]==s2[j]?0:1), \quad min(dp[i-1][j], \quad dp[i][j-1]+1) \}

其中dp[i1][j]dp[i-1][j]表示字符串s1前i1i-1个字符到字符串s2前jj个字符的距离,增加一个字符即可得到,dp[i][j1]dp[i][j-1]表示字符串s1前ii个字符到字符串s2前j1j-1个字符的距离, 删除一个字符即可得到,因此转移都是加1次操作。dp[i1][j1]dp[i-1][j-1]表示字符串s1前i1i-1个字符到字符串s2前j1j-1个字符的距离,如果现在两个字符相同,则不变,否则增加一次修改操作,距离加1.

alt

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
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;
}

复杂度分析:

  • 时间复杂度:O(nm)O(nm),其中nnmm分别为两个字符串的长度,两层遍历
  • 空间复杂度:O(nm)O(nm),辅助数组dp的大小

方法二:动态规划空间优化

具体做法:

我们发现方法一在遍历的时候二维数组只使用到了本行ii及上一行i1i-1,因为我们可以使用滚动数组优化(k=1kk=1-kkk表示行),每次遍历都只有两行,而下标在不断交替使用。每行首列在外循环进入的时候初始化为行号,原理同上。

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
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;
}

复杂度分析:

  • 时间复杂度:O(nm)O(nm),其中nnmm分别为两个字符串的长度,两层遍历
  • 空间复杂度:O(m)O(m),辅助数组dp只有两行,每行长度m+1m+1