如果比较的字符一样呢,稍安毋躁,刚好X的C要与Y的C进行比较,即ABC的子序列集合{"",A,B,C,AB,BC,ABC}与BDC的子序列集合{"",B,D,C,BD,DC,BDC}比较,得到公共子串有“”,B,D 。这时还是与之前的结论一样,当字符相等时,它对应的格子值等于左边与右边与左上角的值,并且左边,上边,左上边总是相等的。这些奥秘需要更严格的数学知识来论证。
假设有两个数组,A和B。A[i]为A的第i个元素,A(i)为由A的第一个元素到第i个元素所组成的前缀。m(i, j)为A(i)和B(j)的最长公共子序列长度。
由于算法本身的递推性质,其实只要证明,对于某个i和j:
m(i, j) = m(i-1, j-1) + 1 (当A[i] = B[j]时)
m(i, j) = max( m(i-1, j), m(i, j-1) ) (当A[i] != B[j]时)
第一个式子很好证明,即当A[i] = B[j]时。可以用反证,假设m(i, j) > m(i-1, j-1) + 1 (m(i, j)不可能小于m(i-1, j-1) + 1,原因很明显),那么可以推出m(i-1, j-1)不是最长的这一矛盾结果。
第二个有些trick。当A[i] != B[j]时,还是反证,假设m(i, j) > max( m(i-1, j), m(i, j-1) )。
由反证假设,可得m(i, j) > m(i-1, j)。这个可以推出A[i]一定在m(i, j)对应的LCS序列中(反证可得)。而由于A[i] != B[j],故B[j]一定不在m(i, j)对应的LCS序列中。所以可推出m(i, j) = m(i, j-1)。这就推出了与反正假设矛盾的结果。
得证。
我们现在用下面的方程来继续填表了。

程序实现
//by 司徒正美
function LCS(str1, str2){
var rows = str1.split("")
rows.unshift("")
var cols = str2.split("")
cols.unshift("")
var m = rows.length
var n = cols.length
var dp = []
for(var i = 0; i < m; i++){
dp[i] = []
for(var j = 0; j < n; j++){
if(i === 0 || j === 0){
dp[i][j] = 0
continue
}
if(rows[i] === cols[j]){
dp[i][j] = dp[i-1][j-1] + 1 //对角+1
}else{
dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) //对左边,上边取最大
}
}
console.log(dp[i].join(""))//调试
}
return dp[i-1][j-1]
}
LCS可以进一步简化,只要通过挪位置,省去新数组的生成
//by司徒正美
function LCS(str1, str2){
var m = str1.length
var n = str2.length
var dp = [new Array(n+1).fill(0)] //第一行全是0
for(var i = 1; i <= m; i++){ //一共有m+1行
dp[i] = [0] //第一列全是0
for(var j = 1; j <= n; j++){//一共有n+1列
if(str1[i-1] === str2[j-1]){
//注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
dp[i][j] = dp[i-1][j-1] + 1 //对角+1
} else {
dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1])
}
}
}
return dp[m][n];
}
内容版权声明:除非注明,否则皆为本站原创文章。
