在前端组件框架里面,很常见的dom处理就是将一个dom树切换为另外一个dom树,现代的mvvm框架基本都是在dom树的每一层上去做diff的。简化的来看就是要以最小的开销从 "abcde" 切换到 "ace"。
首先,我们了解一下基本概念:
1:子序列(subsequence): 一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。例如序列<A,B,C,B,D,A,B>的子序列有:<A,B>、<B,C,A>、<A,B,C,D,A>等。
2:公共子序列(common subsequence): 给定序列X和Y,序列Z是X的子序列,也是Y的子序列,则Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,那么序列Z=[B,C,A]为X和Y的公共子序列,其长度为3。但Z不是X和Y的最长公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均为X和Y的最长公共子序列,长度为4,而X和Y不存在长度大于等于5的公共子序列。对于序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。
3:最长公共子序列:给定序列X和Y,从它们的所有公共子序列中选出长度最长的那一个或几个。
4:子串: 将一个序列从最前或最后或同时删掉零个或几个字符构成的新系列。区别与子序列,子序列是可以从中间抠掉字符的。cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列。
(图示为子序列与子串对比)
ps:上面基本概念转载自https://segmentfault.com/a/1190000012864957(作者:司徒正美);
好了,现在回归算法主题:
例如:
我们有两个字符串,分别为text1和text2
text1 = "ace";
text2 = "abcde";
那么如何求出他们的最长公共子序列?
先用棋盘图来演示:
求最长公共子序列的代码:
讲解:
这里说明一下,为什么会初始化棋盘的第一行以及第一列。
一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序),所以它也可以是空字符串,空字符串比较结果为0。
所以我们初始化棋盘的第一列以及第一行。
接着,我们遍历完棋盘后,去做判断;
假设i=0;j=0;如果text1[0]==text2[0]的话:
if(text1[i] == text2[j]){
dp[i + 1][j + 1] = dp[i][j] + 1
}
那么,数组中的第二行第二列为数组第一行第一列的值+1,
即dp[1][1] = dp[0][0] + 1;
dp[i+1][j+1]=dp[i][j]+1
假设i=0;j=1;如果text1[0]!== text2[1]的话:
if(text1[i] !== text2[j]){
dp[i + 1][j + 1] = Math.max(dp[i][j + 1],dp[i + 1][j]);
}
那么,数组中的第二行第三列的值,取(数组第一行第三列的值和数组的第二行第二列的值)这两个值中大的值,
即dp[1][2] = Math.max( dp[0][2] , dp[1][1] );
dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j])
最终输出棋盘最后一行最后一列的值,就是我们求得的值。
下面附上完整代码:
var text1 = "ace";
var text2 = "abcde";
var longestCommonSubsequence = function(text1,text2){
//初始化棋盘的第一行
let dp = [(new Array(text2.length + 1)).fill(0)];
//这时dp dp=[
// [0,0,0,0,0,0]
// ];
for(let i=0;i<text1.length;i++){
//初始化棋盘第一列
dp[i+1] = [0];
// 这时dp=[
// [0,0,0,0,0,0],
// [0],
// [0],
// [0],
// ]
for(let j=0;j<text2.length;j++){
//双遍历这个表格
if(text1[i]==text2[j]){
dp[i+1][j+1]=dp[i][j]+1
}else{
dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j])
}
}
}
// 这时候dp为
// 0: (6) [0, 0, 0, 0, 0, 0]
// 1: (6) [0, 1, 1, 1, 1, 1]
// 2: (6) [0, 1, 1, 2, 2, 2]
// 3: (6) [0, 1, 1, 2, 2, 3]
return dp[dp.length - 1][dp[0].length - 1];
//返回棋盘最后一行最后一列的结果就是我们想要的最终结果
}
longestCommonSubsequence(text1,text2);
//结果为 3
个人笔记小结,仅帮助记忆,理论性的表达还有很多提升空间。
有看到哪里出现错误的麻烦路过的大牛请指出。谢谢啦。