算法笔记:编辑距离

算法笔记:编辑距离

原题:
给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。
你总共三种操作方法:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

给出 work1=”mart” 和 work2=”karma”
返回 3

下面是两种思路实现,自顶向上方法和自顶向下方法

1.自底向上方法实现

不如将问题简化下,化成规模比较小的问题。
首先,我们先考虑两个空串,那么当然就会出现0次。
若其中有一个为空串,另外一个为单字符的话,若A为空的话,只要执行一次插入操作,就能达到目的。反之只要执行一次删除次数操作。
对于非空串来说,我们先考虑只有单字符的A和B,不相等的话就只需要执行一次替换操作,就可以到达目的。

我们将上面操作视为单字符操作

对于双字符的情况,它可以视作单字符完成后再进行单字符操作的情况。对于有三个字符的,同样视作双字符操作完成后再执行单字符判断操作的情况。以此类推,那么对于任意长度n的字符串,都可以看成n-1字符串完成后再进行单字符操作。

这是一种动态规划的算法。于是我们就可以使用一个result[i][j]数组来表示字符串A的从起始到i的子串到匹配字符串B从起始到j的子串所需要进行的操作数。

那么问题来了,我们应该如何表示单字符操作呢?
正如上面定义所说的。当A为空的话,那么就执行插入。那什么时候会出现这么一种情况?
当A的长度比B要小的时候,那么我们就需要进行插入操作。
当我们用result[i][j]形式表示该操作的时候,就表示为result[i][j] = result[i][j-1]+1
(为什么这里是result[i][j-1] 而不是result[i - 1][j]?
因为我们result[i][j-1]表示A的前i个和B的前j-1的已经匹配好的了,所以当j-1加1后,i就比较小了。所以就需要进行插入操作。

同理,当A的长度比B要大的时候,我们就要需要删除操作
表示为result[i][j] = result[i-1][j] + 1;
那么替换呢?
很简单,两个一样长,如果不相等就替换。
表示为result[i][j] = result[i-1][j-1] + x(x = 0 or 1)

我们可以用下图来表示他们之间的关系
这里写图片描述

迭代算法

    public class Solution {

    public int minDistance(String word1, String word2) {
        int[][] result = new int[word1.length()+1][word2.length()+1];
        //初始化数组
        for(int i = 0 ; i <= word1.length() ; i++){
            result[i][0] = i;
        }

        for(int j = 0 ; j <= word2.length() ; j++){
            result[0][j] = j ;
        }

        for(int i = 1 ; i <= word1.length() ; i++){
            for(int  j = 1; j <= word2.length() ;j++){
                // 单字符操作
                if(word1.charAt(i-1) == word2.charAt(j-1)){
                    result[i][j] = result[i-1][j-1];
                } else {
                    result[i][j] = min(result[i-1][j-1],result[i-1][j],
                        result[i][j-1]) + 1;
                }

            }
        }
        return result[word1.length()][word2.length()];
    }

    public int min(int t1,int t2,int t3){
        int min = t1 > t2? t2 : t1;
        min = min > t3? t3 : min;
        return min;
    }


}

2.自顶向下方法实现

当然,不止那么一种算法。我们刚才是通过从后往前推的方式推导出结果的。
我们也可以换一种思路,从前往后退的方式进行推导。
对于空串的处理,我们和上面的处理是一样的
那么就对于不为空串的字符串就有

  1. 删除A第一个字符后,将A从2到尾的子串和B从1到尾的子串变成相同字符串
  2. 删除B第一个字符后,将A从1到尾的子串和B从2到尾的子串变成相同字符串
  3. 替换A或B第一个字符后,将A从2到尾的子串和B从2到尾的子串变成相同字符串
  4. 增加B第一个字符到A第一个字符后,将A从1到尾的子串和B从2到尾的子串变成相同字符串
  5. 增加A第一个字符到B第一个字符后,将A从2到尾的子串和B从1到尾的子串变成相同字符串

综上,我们可以得出如下结论

  1. 一步操作后,将A从1到尾的子串和B从2到尾的子串变成相同的字符串
  2. 一步操作后,将A从2到尾的子串和B从1到尾的子串变成相同的字符串
  3. 一步操作后,将A从2到尾的子串和B从2到尾的子串变成相同的字符串

那么我们就可以通过递归的方式得出结果

递归算法

    public class Solution {

    public int minDistance(String word1, String word2) {
        // 初始化结果数组
       return caculateDistance(word1,0,word1.length()-1,
       word2,0,word2.length() - 1);
    }

    public int caculateDistance(String word1,int begin1,int end1,String word2,int begin2,int end2){

        if(begin1 > end1){
            if(begin2 > end2){
                return 0;
            }
            else{
                return end2 - begin2 + 1;
            }
        }

        if(begin2 > end2){
            if(begin1 > end1){
                return 0;
            }
            else{
                return end1 - begin1 + 1;
            }
        }
        //相等直接跳下一个
        if(word1.charAt(begin1) == word2.charAt(begin2)){
            return caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2);
        }else {
            //情况1
            int t1 = caculateDistance(word1,begin1,end1,word2,begin2+1,end2);
            //情况2
            int t2 = caculateDistance(word1,begin1+1,end1,word2,begin2,end2);
            //情况3
            int t3 = caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2);
            return min(t1,t2,t3)+1;
        }
    }

    public int min(int t1,int t2,int t3){
        int min = t1 > t2? t2 : t1;
        min = min > t3? t3 : min;
        return min;
    }

}

上面的算法就是《编程之美》里面提供的算法。
不过我们可以发现,它在每一次调用的时候都会进行三次的自我递归调用。不难发现他的时间复杂度是O(3^n)。是一种很糟糕的算法。对于特别大的数据,耗时是非常大的。那么有没有改进措施呢?
该书也给出了一种改进的方法。

如下图是部分展开的递归调用
这里写图片描述

(注:图来源于《编程之美》)

可以发现,圈中的两个子问题被重复计算了。那么我们就像能不能减少计算的量。既然重复计算了,那么第二次计算就是没有必要的。所以我们最有效的方法就是把计算结果存储起来。当第二次调用的时候,我们直接用就好了。
下面是优化后的算法

带记忆的递归算法

    public class Solution {
    //用来记录结果
    int[][] result;

    public int minDistance(String word1, String word2) {
        // 初始化结果数组
        result = new int[word1.length()+1][word2.length()+1];
        for(int i = 0; i < word1.length()+1 ;i++){
            for(int j = 0; j <word2.length()+1 ;j++){
                result[i][j] = -1 ;
            }
        }
       return caculateDistance(word1,0,word1.length()-1,
       word2,0,word2.length() - 1);
    }

    public int caculateDistance(String word1,int begin1,int end1,String word2,int begin2,int end2){

        if(result[begin1][begin2] > 0 ){
            return result[begin1][begin2];
        }

        if(begin1 > end1){
            if(begin2 > end2){
                result[begin1][begin2] = 0;
                return 0;
            }
            else{
                result[begin1][begin2] = end2 - begin2 + 1;
                return result[begin1][begin2];
            }
        }

        if(begin2 > end2){
            if(begin1 > end1){
                result[begin1][begin2] = 0;
                return 0;
            }
            else{
                result[begin1][begin2] = end1 - begin1 + 1;
                return result[begin1][begin2];
            }
        }

        if(word1.charAt(begin1) == word2.charAt(begin2)){
            return caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2);
        }else {

            int t1 = caculateDistance(word1,begin1,end1,word2,begin2+1,end2);
            int t2 = caculateDistance(word1,begin1+1,end1,word2,begin2,end2);
            int t3 = caculateDistance(word1,begin1+1,end1,word2,begin2+1,end2);
            result[begin1][begin2] = min(t1,t2,t3)+1;
            return result[begin1][begin2];
        }
    }

    public int min(int t1,int t2,int t3){
        int min = t1 > t2? t2 : t1;
        min = min > t3? t3 : min;
        return min;
    }

}
发布了36 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_32763643/article/details/77678196
今日推荐