首页 > 代码库 > Edit Distance

Edit Distance

https://oj.leetcode.com/problems/edit-distance/

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character
b) Delete a character
c) Replace a character

解题思路:

这时比较难的一道动态规划题目。难在两点。第一,需要考虑两个字符串的坐标,也就是说是一个二维dp。第二,状态转换方程的推导比较难。

开始,我很快想到LCS(longest common string)的方法,以为只要求出两个字符串的LCS长度, 那么操作步骤就是两个字符串的较长的长度减去LCS长度。于是,写出了下面的代码。

public class Solution {    public int minDistance(String word1, String word2) {        int m = word1.length();        int n = word2.length();                if(m == 0 && n == 0){            return 0;        }        if(m + n == 1){            return 1;        }                int[][] dp = new int[m][n];                if(word1.substring(0, 1).equals(word2.substring(0,1))){            dp[0][0] = 1;        }else{            dp[0][0] = 0;        }                if(m > 1 && (word1.charAt(1) == word2.charAt(0) || word1.charAt(0) == word2.charAt(0))){            dp[1][0] = 1;        }        if(n > 1 && (word1.charAt(0) == word2.charAt(1) || word1.charAt(0) == word2.charAt(0))){            dp[0][1] = 1;        }                for(int i = 1; i < m; i++){            for(int j = 1; j < n; j++){                if(word1.charAt(i) == word2.charAt(j)){                    dp[i][j] = dp[i - 1][j - 1] + 1;                }else{                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);                }            }        }        return Math.max(m, n) - dp[m - 1][n - 1];    }}

这个代码惨败在如下的用例中:

Input:"ab", "bc"
Output:1
Expected:2

可以看到,错位的需要两步。想到这里,我考虑LCS的方法一定是不行的,因为无法区分replace和delete then insert的情况。考虑再三,只能看答案。自己再想,也不难。

定义dp[i][j]为字符串A的前i个字符变成B字符串前j个字符所需要的步骤数量。这时考虑dp[i + 1][j + 1]。如果A[i + 1] == B[j + 1],显然不需要任何步骤,A已经可以是B。那么dp[i + 1][j + 1]=dp[i][j]。如果不等呢?那一定是要将A[i + 1] 替换为B[j + 1],所以步骤要+1。问题是,再哪个基础上加一?

考虑将A[i + 1]变成B[j],那么再变成B[j + 1],就需要再加上B[j + 1]即可,即dp[i + 1][j + 1]=dp[i+1][j] + 1。第二中方案,将A[i]变成B[j + 1],那么就需要删掉A[i + 1]。于是,dp[i + 1][j + 1]=dp[i][j + 1] + 1。第三种方案,将A[i]变成B[j],然后将A[i + 1]替换为B[j + 1],于是dp[i + 1][j + 1]=dp[i+1][j + 1] + 1。下面引用的是我自己尝试的另一种解释方法。

考虑A从A[i]变到A[i + 1]。有三种可以做的方案。以A[i]为基础,将A[i]已经变成了B[j + 1],花费的步骤数为dp[i][j + 1],那么需要再删除A[i + 1]。于是dp[i + 1][j + 1]=dp[i][j + 1] + 1。第二种情况,以A[i + 1]为基础,先将其变成B[j],花费dp[i + 1][j + 1]步,那么需要再加上B[j + 1]。于是dp[i + 1][j + 1]=dp[i+1][j] + 1。第三种方案,先将A[i]变成B[j],再将A[i + 1]替换为B[j + 1]。于是dp[i + 1][j + 1]=dp[i+1][j + 1] + 1。

 

综上,dp[i][j]就是三种情况里最小的,于是=min( dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)。

初始化的时候需要注意,dp[0][i] = i,dp[i][0] = i。因为0个字符要变成i个,必然要add,反之就是delete。代码如下。

public class Solution {    public int minDistance(String word1, String word2) {        int m = word1.length();        int n = word2.length();                if(m == 0 && n == 0){            return 0;        }                int[][] dp = new int[m + 1][n + 1];                for(int i = 0; i <= m; i++){            dp[i][0] = i;        }                for(int i = 0; i <= n; i++){            dp[0][i] = i;        }                for(int i = 1; i <= m; i++){            for(int j = 1; j <= n; j++){                if(word1.charAt(i - 1) == word2.charAt(j - 1)){                    dp[i][j] = dp[i - 1][j - 1];                }else{                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;                }            }        }        return dp[m][n];    }}

这个题目的状态转换方程还是比较难的,看懂后,还需要仔细捉摸才能真正搞懂。这道题其实是NLP(自然语言处理)里的一个课题,本身也是一个叫做Wagner–Fischer的方法。可以参见下面两个网页。

http://en.wikipedia.org/wiki/Edit_distance

http://en.wikipedia.org/wiki/Edit_distance

Edit Distance