编辑距离,dp
题意:
给你两个字符串s1和s2,和三种操作:
1.插入一个字符
2.删除一个字符
3.替换一个字符
问至少要多少次操作才能从s1变换到s2.
思路:
编辑距离,很有用的一个概念.模糊匹配等地方都会出现.
方法是dp,和求最大公共子序列十分像,但是我一直不会.
dp[i][j]表示s1长度为i的前缀和s2长度为j的前缀的编辑距离.
初始化是dp[i][0]=i,dp[0][j]=j
递推公式是dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+(s1[i-1]!=s2[j-1]))
答案是dp[s1.len][s2.len]
递推公式中的三项分别对应删除,插入,替换(或无需替换).
时间复杂度显然是O(n2),空间复杂度也是O(n2),但可以用滚动数组优化到O(n).
证明:
由于我不会,所以证明了一下正确性.
先有四个比较显然的结论:
(1)操作之间的顺序颠倒不影响答案
(2)如果s1和s2有相同的前缀或后缀,那么删掉它们不影响答案
(3)s1的每个字符最多只被操作一次(替换或删除)
(4)如果s1的最后一个字符没被删除,但操作后不是s2的最后一个字符,那么s2的最后一个字符肯定是插入的.
现在枚举最优变换下,s1的最后一个字符要作什么操作.
1.删除
以s1="be",s2="db"为例子,最佳路径可以是"be"->"dbe"->"db".
我们把这个操作放在一开始进行,那么dp["be"]["db"]=dp["b"]["db"]+1(删除)
2.替换
(1)这个字符替换后是s2的最后一个字符
以s1="be",s2="sbd"为例子,最佳路径可以是"be"->"sbe"->"sbd".
我们把这个操作放在一开始进行,那么dp["be"]["sbd"]=dp["b"]["sb"]+1(替换)
(2)这个字符替换后不是s2的最后一个字符
以s1="be",s2="bfd"为例子,最佳路径可以是"be"->"bf"->"bfd".
由于被替换后不是s2的最后一个字符,那么它在s2的最后一个字符肯定是插入的.
我们把插入操作放在一开始进行,此时有dp["be"]["bfd"]=dp["be"]["bf"]+1(插入)
3.没操作
(1)这个字符是s2的最后一个字符
以s1="be",s2="dke"为例子,最佳路径可以是"be"->"dbe"->"dke".
'e'同时是s1和s2的后缀,此时有dp["be"]["dke"]=dp["b"]["dk"](无需替换)
(2)这个字符不是s2的最后一个字符
以s1="be",s2="bad"为例子,最佳路径可以是"be"->"ba"->"bad".
由于'e'是s1的最后一个字符,但在s2并不是最后一个,那么s2的最后一个字符肯定是插入的.
我们把插入操作放在一开始进行,此时有dp["be"]["bad"]=dp["be"]["ba"]+1(插入)
总结:
dp,dp[i][j]表示s1长度为i的前缀和s2长度为j的前缀的编辑距离.