• LeetCode72. 编辑距离


    考虑用一个二维dp数组表示所需的最小操作次数。

    dp[i][j]表示将word1的前i个字符转换为word2的前j个字符所需要的最少操作次数。

    由于操作的顺序对于最后操作的结果没有影响,所以我们假设操作总是从word1的前面字符操作到word1的后面字符。

    1. 如果word1的第i个字符等于word2的第j个字符,所以对于第i个字符实际上是没有操作的,
      那么编辑距离就取决于word1的1 ~ i - 1的字符和word2的1 ~ j - 1的字符,
      所以我们得到: dp[i][j] = dp[i - 1][j - 1]
      这个方程表示当word1的第i个字符等于word2的第j个字符,编辑距离就是将word1的1~i-1个字符改为
      word2的1~j-1的编辑距离。 (至于是怎么编辑的,我们不需要管,我们只需要计算编辑距离就行了,下面同理)

    2. 如果word1的第i个字符和word2的第j个字符不相等。那么有三种操作:

      (1)如果要将word1的 1 ~ i 转换为word2的 1 ~ j 的最后一步是:在word1的1 ~ i个字符之后增加一个字符word1[i + 1] (这个字符必然是word2[j]),这就比将word1的1i转换为word2的1j-1的编辑距离多一(这里可能有点难以理解,这么说吧,word1最开始是1i,word2是1j-1,由于word1转换为word2的最后一步是加入一个字符word1[i + 1]或者说是word2[j],所以编辑距离就是word1的1i到word2的1j-1的编辑距离加一!) 得到状态转移方程: 编辑距离dp[i][j] = dp[i][j - 1] + 1

      (2) 如果要将word1的1 ~ i 转换为word2的 1 ~ j的最后一步是: 删掉word1的第i个字符,那么编辑距离就是将word1的1~i-1个字符转换为word2的1~j个字符的编辑距离加一(加一这部操作表示删除word1[i]), 因此我们得到状态转移方程: dp[i][j] = dp[i - 1][j] + 1

      (3) 如果要将word1的1 ~ i 转换为word2的 1 ~ j的最后一步是: 修改word1的第i个字符,那么编辑距离就是将word1的1i-1个字符转换为word2的1j-1个字符的编辑距离加一。可以这么理解:经过将word1的1i-1个字符转换为word2的1j-1个字符的编辑距离之后,word2多了一个字符word[j],偏偏word1的最后一个字符word[i]和word2[j]不相等,好在我们只需要一步修改操作就可以完成所有转换。 所以状态转移方程是:dp[i][j] = dp[i - 1][j - 1] + 1

    有了状态转移方程,只需要再对dp数组的边界做初始化,然后就可以开始递推啦。

    边界可以这么确定:假设word1和word2两个字符串其中一个字符串为空,另一个字符串长度为x,那么dp[x][0]或者dp[0][x]显然就是x了。
    把长度为x的字符串全部删掉也好,把空字符串加x个字符使得它和另一个字符串相等也好,最少的操作次数必然是x。

    这样我们就找到了数组递推边界,开始考虑递推:
    从上面的第2点(word1的第i个字符不等于word2的第j个字符)的分析可以发现,dp[i][j]是从dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]
    三个状态得到的,而且都是在前一个状态的基础上加一(分别表示添加、删除、修改操作),由于我们的dp数组表示操作的最少次数,
    所以我们要取三个前置状态的最小值再+1。

    因此当word1的第i个字符不等于word2的第j个字符时,我们得到状态转移方程:dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j]) + 1

    如果当word1的第i个字符和word2的第j个字符相等呢,这个情况我们可以单独判断,如果相等,就让dp[i][j]等于dp[i - 1][j - 1]就好了。

    代码如下:

    class Solution {
    public:
        int minDistance(string word1, string word2) {
            int n = word1.size(), 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 i = 1; i <= m; ++i) {      //初始化边界
                dp[0][i] = i;
            }
            for(int i = 1; i <= n; ++i) {
                for(int j = 1; j <= m; ++j) {
                    dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
                    if(word1[i - 1] == word2[j - 1]) {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                }
            }
            return dp.back().back();           //等价于return dp[n][m]; 
        }
    };
    
  • 相关阅读:
    scala中List、Array、ListBuffer、ArrayList、Set
    Spark Pipeline
    Spark MLlib
    minikube
    proxychains 安装
    网络性能测试
    Flexvolume
    kubectl 获取信息
    centos7 莫名重起的问题
    fio 测试磁盘
  • 原文地址:https://www.cnblogs.com/linrj/p/13233244.html
Copyright © 2020-2023  润新知