给定两个字符串word1,word2,将word2变成word1所需的最小步数。规定只能用以下三种操作进行修改,且每种操作的步数都计为1.
op1:插入一个字符
op2:删除一个字符
op3:替换一个字符
例:
word1 = word, word2 = wore, 则将wore的e替换为d则可。
word1 = word,word2 = wor,则在wor的后面插入一个d则可。
word1 = word, word2 = wordd,则删除wordd后面的d则可。
这里只是简单的说明下这三种操作,算法中考虑的东西比这复杂,但是基本的操作是如此。
/*********************************************************************************************************************************************************/
在leetcode里我们见过很多类似模式的题,其模式可简化为:给定两个串str1,str2,按某一规则,从str1得到str2.这类问题基本都是用动态规划来解决。既然是动态规划,我们来具体分析一下。
我们用d(s1,s2)来表示s1和s2的”距离“。
1) d('','') = 0,'' 表示空串
2) d(s,'') = d('', s) = |s| , s 表示 s 的长度
3) d(s1 + ch1,s2 + ch2)
=min( d(s1, s2) + ch1== ch2 ? 0 : 1,
d(s1 + ch1, s2) + 1,
d(s1, s2 + ch2) + 1)
第一种情况好理解,都是空的时候不需要任何操作,二者相等。
第二种情况也好理解,若s1是空,只需要将s2中的字符全部删除,需要的操作步数是s2的长度;若s2是空,则只需要在s2中插入s1对应的字符,操作步数为s1的长度。
第三种情况,若两者都不为空,那么这两个字符串肯定都有最后一个字符,我们分别用ch1和ch2来表示,我们需要做的就是将s1 + ch1 变成 s2 + ch2,对于最后一个字符:
a)若ch1 和 ch2 相同,那么这一步我们不用进行修改,只需要将s1变成s2就可以了。这样,整体的操作步数为 d(s1,s2)
b)若ch1 和 ch2 不同,我们有三种修改方式:
i)将ch1 替换为 ch2,那么剩下的事情就是将 s1 变成 s2,整体的操作步数为 d(s1, s2) + 1。
ii)将ch1 删除,那么剩下的事情就是将 s1 变成 s2 + ch2,整体的操作步数为 d(s1, s2 + ch2) + 1
iii)在s1 + ch1 后面插入ch2,ch2就匹配了,那么剩下的事情就是将 s1 + ch1 变成 s2,整体操作步数为d(s1 + ch1,s2) + 1。
这样理完思路,我们写出动态规划的每个部分:
状态:m[ i,j] = d(s1[1..i ],s2[1,j ])
初始条件:m[0,0] = 0
m[ i,0] = i
m[ 0 ,j ] = j
状态转移方程:m[i, j ] = min( m[i - 1, j - 1] + s1[ i - 1] == s2[ j - 1] ? 0 : 1 // replace or not
m[ i - 1, j ] + 1, //delete
m[ i, j - 1] + 1, //insert
最终结果:m[ |s1|, |s2| ]。
例:
通过状态转移方程,我们看到当前行的操作只用到当前行和前一行,所以可以不用定义一个|s1| * | s2 | 大小的表。
//code
/* 1)state: dp[i,j]--word1[1..i],word2[1..j], the minimum ops we have done. 2)state trans function: dp[i, j] = min(dp[i - 1, j - 1] + (word1[i] == word2[j] ? 0 : 1),//replace or do nothing dp[i-1,j] + 1,//delete the current word in word1 dp[i, j - 1] + 1)//insert the same word as word2[j - 1] into word1. 3)initial: dp[0,0] = 0 dp[i,0] = i dp[0,j] = j 4)final state: dp[|word1|,|word2|] */ int minDistance(string word1, string word2) { //equal do nothing if( 0 == word1.size() && 0 == word2.size()) return 0; //delete all the words in word2 else if(word1.size() == 0) return word2.size(); //insert all the words of word1 into word2 else if(word2.size() == 0) return word1.size(); int len_word1 = word1.size(); int len_word2 = word2.size(); vector<int> dp_pre(len_word1 + 1, 0); //initial for (int i = 0; i <= len_word1; ++i) dp_pre[i] = i; vector<int> dp_cur(len_word1 + 1, 0); for (int i = 1; i <= len_word2; ++i) { dp_cur[0] = i; for (int j = 1; j <= len_word1; ++j) { int num_repalce = 0; if(word1[j - 1] != word2[i - 1]) num_repalce = 1; dp_cur[j] = min(min(dp_cur[j - 1] + 1, dp_pre[j] + 1),dp_pre[j - 1] + num_repalce); } dp_pre = dp_cur; } return dp_pre[len_word1]; }
时间复杂度为O(|s1| * |s2|) ,O(n^2), 空间复杂度O(n)。
1)文件修改:文件在进行修改了以后,如果用修改后的内容完全替换修改前的内容,就很费时,我们可以通过这种方式修改最少的内容,尤其是通过网络的方式。
2)远程桌面:A主机远程B主机,那么B主机的屏幕要显示在A主机的屏幕上,但是大部分时候,B主机屏幕的变化总是局部的,所以只需要在A主机上变化B主机变化的部分。
还有很多,不一一列举。
本文借鉴参考了http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Dynamic/Edit/ 这篇文章,写的很好。
欢迎转载,探讨指正,转载及其他,请说明出处。