在研究 React list diff
算法的过程中,发现了一个有趣的问题,即给定任意两个字符串 s1 与 s2,求将 s1 通过以下操作转换成 s2 的最少步骤 (每执行一次操作,s1 到 s2 的编辑距离加 1)。
- 插入一个字符
- 删除一个字符
- 替换一个字符
具体举例,现有 s1='abc'
和 s2='cbef'
,求 s1 到 s2 的最短编辑距离 dis(记 dis[m][n] 为 s1<[1]..[m]> 到 s2<[1]…[n]>的最短编辑距离)。下表展示了 s1 到 s2 不同长度子字符串的最短编辑距离:
s1\s2 | 0(‘’) | 1(‘c’) | 2(‘cb’) | 3(‘cbe’) | 4(‘cbef’) |
---|---|---|---|---|---|
0(‘’) | dis[0][0]=0 | dis[0][1]=1 | dis[0][2]=2 | dis[0][3]=3 | dis[0][4]=4 |
1(‘a’) | dis[1][0]=1 | dis[1][1]=1 | dis[1][2]=2 | dis[1][3]=3 | dis[1][4]=4 |
2(‘ab’) | dis[2][0]=2 | dis[2][1]=2 | dis[2][2]=1 | dis[2][3]=2 | dis[2][4]=3 |
3(‘abc’) | dis[3][0]=3 | dis[3][1]=2 | dis[3][2]=2 | dis[3][3]=2 | dis[3][4]=3 |
从上表可以看出两个规律:
- 若其中一个字符串为空,则最短编辑距离即为另一个字符串的长度,即 dis[m][0]=m 或 d[0][n]=n
若已知 dis[m-1][n],dis[m][n-1] 与 dis[m-1][n-1]
- s1[m] === s2[n],则 dis[m][n] = dis[m-1][n-1]
- s1[m] !== s2[n],则 dis[m][n] = min(dis[m-1][n], dis[m][n-1], dis[m-1][n-1]) + 1。
比如 ‘abc’ -> ‘cac’,由于最后一个字符 ‘c’ 相同,则 ‘abc’ -> ‘cac’ 等价于 ‘ab’ -> ‘ca’,对于 ‘ab’ -> ‘ca’,最后一个字符并不相同,那么 ‘ab’ -> ‘ca’ 可以通过三种途径达到:
- 先从 ‘ab’ 中删除 ‘b’,然后让 ‘a’ -> ‘ca’
- 先从 ‘ca’ 中删除 ‘a’,然后让 ‘ab’ -> ‘c’
- 先让 ‘a’ -> ‘c’,然后将 ‘ab’ 中的 ‘b’ 替换成 ‘ca’ 中的 ‘a’
综上可知,无论是 ‘a’ -> ‘ca’,’ab’ -> ‘c’ 还是 ‘a’ -> ‘c’ 都需要经过额外的一步编辑,因此只要在求得三种方式最短编辑距离的最小值之后再加 1 即可。
贴上源码:
|
|