最近在leetcode上碰到了一个要求用最少的步数删除某些字符最终使得两个字符串一样的问题,原问题如下:
Given two words word1 and word2, find the minimum number of steps required to make word1 and word2 the same, where in each step you can delete one character in either string.
Example 1:
Input: “sea”, “eat”
Output: 2
Explanation: You need one step to make “sea” to “ea” and another step to make “eat” to “ea”.
后来发现这个问题就是算法导论动态规划中的最长公共子序列问题,当时对于最长公共子序列的理解不深,现在再来回归下。
给定序列
X=<x1,x2,x3,x4,...,xn>
, 另一个序列Z =
<z1,z2,....,zk>
<script type="math/tex" id="MathJax-Element-7830">
</script>, 如果存在一个严格递增的序列X的下标序列为
<i1,i2,...,ik>
<script type="math/tex" id="MathJax-Element-7831">
</script>,对所有j = 1, 2, 3 …, k,满足
Xij=Zj
,则称Z为X的一个子序列。
最长公共子序列问题:对序列X和Y,如果存在一个序列Z使得Z是X和Y的子序列,且是最长的,则称Z为X和Y的最长子序列LCS(longest common subsequence)。
最长公共子序列具有这样的性质:令X =
<x1,x2,x3,x4...,xm>
<script type="math/tex" id="MathJax-Element-7833">
</script>, Y =
<y1,y2,y3,...yn>
<script type="math/tex" id="MathJax-Element-7834">
</script>为两个序列,Z =
<z1,z2,...zk>
<script type="math/tex" id="MathJax-Element-7835">
</script>为X和Y的任意LCS。
1. 如果
xm=yn
那么
zk=xm=yn
, 且
zk−1
为
xm−1
和
yn−1
的LCS。
2. 如果
xm≠yn
,若
zk≠xm
意味着Z是
xm−1
和Y的一个LCS。
3. 如果
xm≠yn
,若
zk≠yn$意味着Z是
y_{n-1}$和X的一个LCS。
这个性质说明两个序列的LCS包含两个序列的前缀的LCS。也就是说我们可以通过求出它的前缀的子问题从而来求解原问题。
令
c[i,j]
表示
Xi
和
Yj
两个序列的LCS的长度,显然如果i = 0或者j = 0,即一个序列长度为0,则最长公共子序列也为0,C[i, j] = 0,根据LCS性质,可得如下公式:
有了这个公式我们就可以开始写代码了,用一个二维数组记录c[i,j]记录 Xi 和 Yj 两序列的LCS,通过对记录子问题的解来得到原问题的解。
public class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] c = new int[m+1][n+1];
for(int i = 0; i <= m; i++){
c[i][0] = 0;
}
for(int j = 0; j <= n; j++){
c[0][j] = 0;
}
for(int i = 1; i <= m ;i++){
for(int j = 1; j <= n; j++){
if(word1.charAt(i-1) == word2.charAt(j-1)){
c[i][j] = c[i-1][j-1] + 1;
}else{
if(c[i-1][j] > c[i][j-1]){
c[i][j] = c[i-1][j];
}else{
c[i][j] = c[i][j-1];
}
}
}
}
return m + n - 2*c[m][n];
}
}
通过计算两个字符串的LCS, 然后使用两个字符串的长度和减去LCS的两倍,即可得到最小步数。
代码时间复杂度为
O(mn)
,空间复杂度为
O(mn)
。