背景简介
什么是子序列?
某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列
大概意思能在父序列串中按照对应的前后顺序找到的序列 就是子序列
举个例子
X {A,B,C,B,D,A,B}
Y {B,D,C,A,B,A}
DA 是x的子序列 AD不是
算法思路
假定我们称
- 最长公共子序列是lcs
- X的长度为m
- Y的长度为n
当只有1个字符的时候,那么就是判断x1等于y1否 假定最终lcs b1
当有2个字符的时候,从后往前遍历 如果x2=y2 那么只要求之前的lcs再加上x2就是最终lcs 假定为b2
当有3个字符的时候,从后往前遍历 如果x3=y3 那么只要求b2+x3就是最终lcs b3
…
以此类推
再来看下 如果Xm!=Yn
那么就是求Xm-1,Yn Xm,Yn-1两种组合的最大lcs
假定c[i][j]
表示i,j区间内的lcs长度
c[i][j]
如果x[i]=y[j]
那么c[i][j]=c[i-1][j-1]+1
如果x[i]!=y[j]
那么c[i][j]=max(c[i-1][j],c[j-1][i])
仅仅有这张表是不够的,我们还需要记录对应的查找顺序 也就是上述三种情况
- i-1,j-1的方向
- i-1,j的方向
- i,j-1的方向
算法过程
新建两张表
let b = newTable(m + 1, n + 1);
:顺序表
let c = newTable(m + 1, n + 1);
:长度表
遍历m,n长度所有的情况 然后判断三种情况就可以了
遍历
for (let i = 1; i < m + 1; i++){
for (let j = 1; j < n + 1; j++) {
}
}
3种情况
if (x[i - 1] == y[j - 1]) {
c[i][j] = c[i - 1][j - 1] + 1;
b[i][j] = "c";
} else if (c[i - 1][j] >= c[i][j - 1]) {
c[i][j] = c[i - 1][j];
b[i][j] = "b";
} else {
c[i][j] = c[i][j - 1];
b[i][j] = "a";
}
最后输出最长子序列 依然是递归查表就可以了
代码实现
//生成二维数组
function newTable(m, n) {
let arr = [];
for (let i = 0; i < m; i++) {
let arrInside = [];
for (let j = 0; j < n; j++) {
arrInside.push(0);
}
arr.push(arrInside);
}
return arr;
}
// 生成序列表
function LcsLength(x, y) {
let m = x.length;
let n = y.length;
let b = newTable(m + 1, n + 1);
let c = newTable(m + 1, n + 1);
for (let i = 1; i < m + 1; i++) {
for (let j = 1; j < n + 1; j++) {
if (x[i - 1] == y[j - 1]) {
c[i][j] = c[i - 1][j - 1] + 1;
b[i][j] = "c";
} else if (c[i - 1][j] >= c[i][j - 1]) {
c[i][j] = c[i - 1][j];
b[i][j] = "b";
} else {
c[i][j] = c[i][j - 1];
b[i][j] = "a";
}
}
}
return { b, c };
}
//最长公共子序列
function getLcs(X, Y) {
const b = LcsLength(X, Y).b;
let str = "";
function select(b, X, i, j) {
if (i == 0 || j == 0) return;
if (b[i][j] == "c") {
select(b, X, i - 1, j - 1);
str = str + X[i - 1];
} else if (b[i][j] == "b") {
select(b, X, i - 1, j);
} else {
select(b, X, i, j - 1);
}
}
select(b, X, X.length, Y.length);
return str;
}
module.exports = getLcs;