6.经典动态规划:编辑距离

编辑距离(LeetCode 72题 难度:困难)

编辑距离问题就是给我们两个字符串s1s2,只能用三种操作,让我们把s1变成s2,求最少的操作数。需要明确的是,不管是把s1变成s2还是反过来,结果都是一样的,所以后文就以s1变成s2举例。

思路分析

dp数组的定义

		dp[i,j]:就是s1[0...i],和s2[0...j]的最小编辑距离(代价最小)
		比如dp[2][4]=2的含义,就是这样的字符串代表长度 S1="ac"和S2=“babc” 

解决两个字符串的动态规划问题,一般就是用两个指针i,j,分别指向两个字符串的最后,然后一步一步向前走,缩小问题规模

设两个字符串分别为 "rad" 和 "apple",为了把s1变成s2,算法会这样进行:

选择

	根据上面的情况,发现 操作不只有三个,其实还有四个操作,那就是啥也不做
	比如这种情况 S1[I]==S2[J]

补充

	还有一个很容易处理的问题 :
		  就是j走完了S2,如果i还没有走完的话,只能删除s1的多余节点了
		  类似:如果i走完了s1,j还没有走完s2,那就只能用插入操作把s2的剩下的字符全部插入到s1。下面会看到,这两种情况就是算法的base case

代码详解

先梳理一下之前的思路:
base case 是i走完s1j走完s2,可以直接返回另一个字符串剩下的长度。
对于每对儿字符s1[i]s2[j],可以有四种操作:

if s1[i] == s2[j]:  
	 啥都别做(skip)  
	 i, j 同时向前移动  
else:  
	 三选一:  
	 插入(insert)  
	 删除(delete)  
	 替换(replace)

有这个框架,问题就已经解决了。读者也许会问,这个「三选一」到底该怎么选择呢?很简单,全试一遍,哪个操作最后得到的编辑距离最小,就选谁。这里需要递归技巧,理解需要点技巧,先看下代码:

public int minDistance(String word1, String word2) {  
    char[] s1 = word1.toCharArray();  
    int m = s1.length;  
    char[] s2 = word2.toCharArray();  
    int n = s2.length;  
    int[][] dp = new int[m+1][n+1];  
    //base  
 	for (int i = 1; i <=m; i++) {  
        dp[i][0]=i;  
    }  
    for (int i = 1; i <=n; i++) {  
        dp[0][i]=i;  
    }  
    //自底向上求解  
 	for (int i = 1; i <= m; i++) {  
        for (int j = 1; j <=n ; j++) {  
            if(s1[i-1]==s2[j-1]){  
				//如果 相等 就缩小指针 ,转为子问题
                dp[i][j]=dp[i-1][j-1];  
            }else {  
				//做哪种操作 代价最小
                dp[i][j]=Math.min(dp[i-1][j]+1,Math.min(dp[i][j-1]+1,dp[i-1][j-1]+1));  
            }  
        }  
    }  
    return dp[m][n];  
}

dp[i-1][j-1]解释

	本来就相等,不需要任何操作,缩小指针
	 s1[0..i] 和 s2[0..j] 的最小编辑距离等于  
	s1[0..i-1] 和 s2[0..j-1] 的最小编辑距离  
	也就是说 dp(i, j) 等于 dp(i-1, j-1)

dp(i, j - 1) + 1, # 插入

		我直接在 s1[i] 插入一个和 s2[j] 一样的字符  
		那么 s2[j] 就被匹配了,前移 j,继续跟 i 对比  
		别忘了操作数加一

dp(i - 1, j) + 1, # 删除

			我直接把 s[i] 这个字符删掉  
			前移 i,继续跟 j 对比  
			操作数加

dp(i - 1, j - 1) + 1 # 替换

		我直接把 s1[i] 替换成 s2[j],这样它俩就匹配了  
		同时前移 i,j 继续对比  
		操作数加一
posted @ 2021-07-05 23:15  宋佳强  阅读(145)  评论(0编辑  收藏  举报