LeetCode72. 编辑距离—字符串类动态规划

题目概述

LeetCode72. 编辑距离—字符串类动态规划_第1张图片
题目链接:点我做题

题解

  这个题也是离谱。。。主要是要把状态定义出来并且把状态转移方程写出来太困难了,不过不要紧,这篇题解的目的就是为了给第一次做这个题的人理清思路、给复习的人(疑似只有我)快速回忆思路用的,因此会讲清楚这个题的思路。
  首先,字符串有关的dp题目,状态一般都设置成从第一个字符到第i个字符的反映问题的东西,这里有两个字符串,因此考虑设置二维数组 f ( i , j ) f(i,j) f(i,j),看看题意是求 w o r d 1 word1 word1 w o r d 2 word2 word2的编辑距离,那么 f ( i , j ) f(i,j) f(i,j)就代表考虑 w o r d 1 word1 word1的前i个字符变到 w o r d 2 word2 word2的前j个字符的编辑距离。假设 w o r d 1 word1 word1的长度是n, w o r d 2 word2 word2的长度是m,那么我们要的答案就是 f ( n , m ) f(n,m) f(n,m).
  建出状态来以后,要考虑的就是状态转移方程,就要回到题目中允许的三种操作:插入一个字符、删除一个字符、替换一个字符。再考虑一个对称性的问题,word1变到word2的编辑距离和word2变到word1的编辑距离显然是相等的,因为每一步都是可逆的,那么我们求编辑距离可以同时对word1操作、对word2操作,当word1和word2相同的时候,总操作次数就是编辑距离。
  如果上面这一大串没看懂,没关系,意思就是说word1和word2可以双向奔赴,思考时不必保持word2不变让word1变成word2。
  破处了这个枷锁以后,我们再来看这个题,显然word1和word2同时都能操作的话, 3 ∗ 2 = 6 3*2=6 32=6,就有6种操作了,但是考虑到对称性:word1插入一个字符等价于word2删除一个字符、word2插入一个字符等价于word1删除一个字符、word1替换一个字符等价于word2替换一个字符。所以实际上可以只用三种操作来描述:

  1. w o r d 1 word1 word1插入一个字符:
  2. w o r d 2 word2 word2插入一个字符
  3. w o r d 1 word1 word1替换一个字符
      有了这些操作我们再来联想状态转移方程,既然每次只能变一个字符,那么 f ( i , j ) f(i,j) f(i,j)一定是由 f ( i − 1 , j ) 、 f ( i , j − 1 ) 、 f ( i − 1 , j − 1 ) f(i - 1,j)、f(i,j-1)、f(i-1,j-1) f(i1,j)f(i,j1)f(i1,j1)这三个状态转移来的。

  考虑 f ( i − 1 , j ) f(i-1,j) f(i1,j)变成 f ( i , j ) f(i,j) f(i,j),怎么变呢, f ( i − 1 , j ) f(i-1,j) f(i1,j)的意思是由 w o r d 2 word2 word2的前j个字符变成 w o r d 1 word1 word1的前i-1个字符的编辑距离,那然后再让此时的 w o r d 1 word1 word1的前i-1个字符再插入一个 w o r d 1 word1 word1的第i个字符不就行了.
  因此此时编辑距离等于由word2的前j个字符变为word1的前i-1个字符的编辑距离加上word1的前i-1个字符再插入word1的第i个字符这一操作的编辑距离,因此如果由 f ( i − 1 , j ) f(i-1,j) f(i1,j)递推到 f ( i , j ) f(i,j) f(i,j),编辑距离就是 f ( i , j ) = f ( i − 1 , j ) + 1 f(i,j) = f(i - 1,j) + 1 f(i,j)=f(i1,j)+1.
  考虑 f ( i , j − 1 ) f(i,j - 1) f(i,j1)变成 f ( i , j ) f(i,j) f(i,j),同上,先让word1的前i个字符变为word2的前j-1个字符,然后让word2的前j-1个字符执行插入word2的第j个字符的操作就可以了,因此此时的编辑距离就等于由word1的前i个字符变为word2的前j-1个字符的编辑距离 f ( i , j − 1 ) f(i,j-1) f(i,j1)再加上插入word2的第j个字符这一操作的编辑距离1,即 f ( i , j ) = f ( i , j − 1 ) + 1 f(i,j)=f(i,j-1)+1 f(i,j)=f(i,j1)+1
  考虑 f ( i − 1 , j − 1 ) f(i-1,j-1) f(i1,j1)变成 f ( i , j ) f(i,j) f(i,j),这里也只剩一种操作了,就是替换,首先让word1的前i-1个字符变为word2的前j-1个字符,然后将word1的第i个字符替换为word2的第j个字符(如果word1的第i个字符和word2的第j个字符相同则不用替换),这样就完成了由word1的前i个字符变为word2的前j-1个字符。
  此时编辑距离就等于由word1的前i-1个字符变为word2的前j-1个字符的编辑距离 f ( i − 1 , j − 1 ) f(i-1,j-1) f(i1,j1)再加上word1的第i个字符替换为word2的第j个字符这一操作的编辑距离1(如果word1的第i个字符和word2的第j个字符相等就不用加上这一步了),
i f ( w o r d 1 [ i − 1 ] = = w o r d 2 [ j − 1 ] ) , f ( i , j ) = f ( i − 1 , j − 1 ) e l s e , f ( i , j ) = f ( i − 1 , j − 1 ) + 1 if (word1[i - 1] == word2[j - 1]),f(i,j) = f(i - 1,j - 1)\\ else,f(i,j) = f(i - 1,j - 1) + 1 if(word1[i1]==word2[j1]),f(i,j)=f(i1,j1)else,f(i,j)=f(i1,j1)+1
  由于求的是最小编辑距离,因此f(i,j) = min(1.,2.,3.).
  接下来考虑初始条件,如果一个word1的前0个字符(也就是个空字符)变为word2的前j个字符,显然只有把word2的j个字符依次插入这一种途径了,因为编辑距离就是执行j次插入的编辑距离: f ( 0 , j ) = j f(0,j) = j f(0,j)=j;
  同理,如果把word2的前0个字符变为word1的前i个字符,编辑距离为 f ( i , 0 ) = i f(i,0)=i f(i,0)=i;
  这个dp优化不了空间复杂度,因为状态转移时同时有 f ( i − 1 , j − 1 ) f(i - 1,j - 1) f(i1,j1) f ( i , j − 1 ) f(i,j-1) f(i,j1)这两项,这要求dp[j - 1]同时储存上一行的 f ( i − 1 , j − 1 ) f(i - 1,j - 1) f(i1,j1)和本行的 f ( i , j − 1 ) f(i,j - 1) f(i,j1),前者要求内层循环从大到小遍历,后者要求内层循环从小到大遍历,矛盾。
代码:

class Solution {
public:
    int minDistance(string word1, string word2) 
    {
        /*
        f(i,j) 考虑word1的前i个字符变成word2的前j个字符的编辑距离
        若word1的长度为n word2的长度为m
        答案就是f(n,m)
        首先要明确 六种操作可以归类为3种操作
        对word1插入一个字符等价于对word2删除一个字符
        对word2插入一个字符等价于对word1删除一个字符
        对word1替换一个字符等价于对word2替换一个字符
        三种操作:
        1.对word1插入一个字符
        2.对word2插入一个字符
        3.对word1替换一个字符
        然后来分析状态转移方程
        1.f(i - 1,j)变到f(i,j),
        这个过程是word2的前j个字符变到word1的前i-1个字符,然后再让word1的前i-1个字符插入第i个字符
        显然编辑距离是由word2的前j个字符变为word1的前i-1个字符的编辑距离再加word1后面再插入一个字符的1,
        即f(i,j) = f(i - 1,j) + 1
        2.f(i,j - 1)变到f(i,j),
        这个过程是word1的前i个字符变成word2的前j-1个字符,然后再让word2的前j-1个字符插入第j个字符
        显然编辑距离是前一步的编辑距离再加1,
        即f(i,j) = f(i,j - 1) + 1
        3.f(i - 1, j - 1)变到f(i,j),
        这个过程是修改word1的第i个字符使其与word2的第j个字符相等,然后让word1的前i-1个字符变为word2的前j-1个字符
        如果本来word1[i - 1] == word2[j - 1] 那就不用做这一次又该操作了
        所以f(i,j) = f(i - 1, j - 1), if word1[i - 1] == word2[j - 1]
            f(i,j) = f(i - 1, j - 1) + 1, else
        只有这三种变化方法 所以f(i,j) = min(1.2.3.)
        初始条件 f(i, 0) = i f(0,j) = j
        */
        int n = word1.size();
        int m = word2.size();
        vector<vector<int>> dp(n + 1, vector<int>(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 (word1[i - 1] != word2[j - 1])
                {
                    dp[i][j] = mymin(dp[i - 1][j], 
                    dp[i][j - 1], dp[i - 1][j - 1]) + 1;
                }
                else
                {
                    dp[i][j] = mymin(dp[i - 1][j] + 1, 
                    dp[i][j - 1] + 1, dp[i - 1][j - 1]);
                }
            }
        }
        return dp[n][m];
    }
    int mymin(int a, int b, int c)
    {
        if (a < b)
        {
            if (a < c)
            {
                return a;
            }
            else
            {
                return c;
            }
        }
        /*a > b*/
        else
        {
            if (b > c)
            {
                return c;
            }
            else
            {
                return b;
            }
        }
    }
};

时间复杂度: O ( m ∗ n ) O(m*n) O(mn)
空间复杂度: O ( m ∗ n ) O(m*n) O(mn)

你可能感兴趣的:(LeetCode刷题,动态规划,算法,字符串,C++,leetcode)