最长公共子序列(Longest Common Subsequence):序列 S 是两个或多个已知序列的子序列,且是所有符合条件序列中最长的,则 S 称为已知序列的最长公共子序列。
最长公共子串要求满足条件的序列是连续的。
最长递增子序列:在给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递增,且这个子序列的长度尽可能地大。最长递增子序列中的元素在原序列中不一定是连续的。
一般的LCS问题(即任意数量的序列)是NP问题,但当序列的数量确定时,问题可以使用动态规划在多项式时间内解决。 最长公共子序列问题存在最优子结构,将问题分解成更小,更简单的“子问题”,这个子问题可以分成更多的子问题,因此整个问题就变得简单了。
最长公共子序列十分实用,可以描述两段文字之间的“相似度”,对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。
两个序列 X、Y :
设有二维数组 f[i,j] 表示 X 的 i 位和 Y 的 j 位之前的最长公共子序列的长度,则有:
f[1][1] = same(1,1);
f[i,j] = max{f[i-1][j -1] + same(i,j),f[i-1,j],f[i,j-1]};
same(a,b) 当 X 的第 a 位与 Y 的第 b 位相同时为“1”,否则为“0”。
二维数组中最大的数便是最长公共子序列的长度,依据数组回溯,便可找出最长公共子序列。
该算法的空间、时间复杂度均为O(n^2),经过优化后,空间复杂度可为O(n)。
求两个字符串最长连续公共子串:
解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0,再求出对角线最长的1的序列,其对应位置就是最长匹配子串的位置。
如下图,在对角线上,连续的1就代表了两字符串对应的位置连续相等。
从该矩阵中找到最长的全为1的对角线,就找到了最大公共子串,分别找出对应的字符即可求出这个最长子串。上图有两个最长的公共子串,bab与aba。
- 矩阵存放两个字符串每个字符之间的匹配情况,相同为1,不同为0。
- 求矩阵中连续的最长的值为1的对角线,这个对角线就是公共子串的长度和位置(行列索引值)
改进:
字符串匹配矩阵很简单,如何找到最长的对角线是一个比较麻烦的问题。
可在填充这个矩阵值时,把公共子串的最大长度的值填入相应的位置。就是在上图基础上把最长的对角线的值改为1.2.3…,代表了公共子串的长度。
每个元素的值 m[i][j]
代表的是子串 s1
的前 i 部分和子串s2
的前 j 部分中最大公共子串的长度;
这个矩阵中最大的元素的值就代表了公共子串的最大长度,通过这个最大元素的行列索引就可以定位到这个公共子串在原字符串中的结束位置,通过长度就可以反推出公共子串的内容了。
当两个字符串中找到匹配的字符之后,s1[i]=s2[j]
,填入矩阵[i,j]位置的值取决于它的左上方的值,填入的值为左上方的值+1,代表这个字符可以加入现有的公共子串。
求两个字符串最长公共子序列(可不连续):
整个矩阵求出之后,右下角的元素值就是字符串a和b的最大公共子序列的长度。
从右下角开始
(1)当前元素等于其左上角加1,当前元素行列相等,是公共子序列中的一个,保存下来。
(2)当前元素来自上方或左方,当前元素不保存。
从5开始,形成如图所示的回溯路线,只有斜向上的箭头对应的行元素纳入最大公共子序列,求得最大公共子序列为ababa。
矩阵记录两个字符串中匹配情况,若是匹配则为左上方的值加1,否则为左方和上方的最大值。
矩阵记录转移方向,然后根据转移方向,回溯找到最长子序列。
参考: