![b0a1fa8cc15fe086dd115e34c5fa479f.png](https://img-blog.csdnimg.cn/img_convert/b0a1fa8cc15fe086dd115e34c5fa479f.png)
最长公共子序列(Longest-Common-Subsequence)是寻找两个字符串中共同的最长子序列。对于一个数列S,如果分别是多个或者多个已经数列的子序列,且它是所有符合此条件序列中最长的,则序列S则为被称为最长公共子序列。
相比最长公共子序列,还有一个类似的最长公共子串(Longest-Common-Substring),两者比较相似,不同点在于其中最长公共子串针对的是连续的子序列,例如:abcd与abde的最长公共子序列是abd,而最长公共字串是ab,最长公共子序列不一定是唯一的,但是他们的长度一定是相同的。动态规划的方式是最长公共子序列和最长公共字串解决问题过程中的核心思想,本文将着重探讨最长公共子序列。
最长公共子序列也是也是一种路径寻找算法,它用于发现字符串之间的最长公共子序列,这个寻找过程是通过路径的方式获得。
1、最长公共子串
理解最长公共子串是深入理解最长公共子序列的基础,最长公共子串问题的解决方法有很多,由于最长公共子串一定是连续的,因此最坏情况下,可以将两个字符串中的较短字符串的所有子集求解出,然后将所有子集按照子集字符串的长度由长到短的顺序,在较长字符串中进行比较,如果发现较长字串包含某个子串,则该子串即为最长公共子串。
但是上述方法的时间复杂度较高,代价相对较大,因此可以采用如下的方式分析最长公共子串,例如求字符串“connect”与“contact”:
(1)基于“connect”与“contact”构造关系矩阵,将每一个字符串拆解为单个字母的形式,默认值全部为0,如下表所示。
![07adc57332950fcfaa40b66a7a6fc213.png](https://img-blog.csdnimg.cn/img_convert/07adc57332950fcfaa40b66a7a6fc213.png)
(2)对矩阵中的每个值,如果当前位置对应的两个字母如果相等,则将左斜上方的值加一,作为当前的值,计算后如下表所示。
![38129204e590b7a85a5f64a978393fc5.png](https://img-blog.csdnimg.cn/img_convert/38129204e590b7a85a5f64a978393fc5.png)
上表中所示的灰色方格中的内容,即为存在相等的字母,连续的灰色方格构成的斜线越长说明连续相同的字母越长,求得的最长斜线即为最长公共子序列。
根据上表,可以通过两种方式获得最长公共字串。
第一种方式:由于最长公共子串一定是出现连续灰色方格构成的斜线上,因此可以从最长的斜线(表格的对角线)开始扫描,以获得最长的斜线;
第二种方式:通过记录矩阵中的最大值所在的位置获得最长公共子串,例如获得是矩阵中的最大值为3,而3的位置用二维坐标的方式表示则是在[2,2]的位置,因此只需要在[2,2]的位置向斜上方推进两步即可,因此[2,2],[1,1],[0,0]对应的字母的逆序即为最长公共子串“con”。
显然第二种方式的时间复杂度最低,也非常易于实现。通过上述计算,可以发现最长公共子串的计算方式相对较为简单,利用矩阵的易于理解、计算的优势,很快就可以计算出最长公共子串,矩阵经常在两两关系的计算中发挥着重要作用。
2、最长公共子序列
用数组X[m]和Y[n]表示两个长度分别为m和n的字符串,在引入二维数组Z[m][n]记录X[m]与Y[n]的最长公共子序列长度,则:
![7fbe024403ced11ab4e9f5f6ada6ebda.png](https://img-blog.csdnimg.cn/img_convert/7fbe024403ced11ab4e9f5f6ada6ebda.png)
通过上述公式可以发现,最长公共子序列的计算思想依然是动态规划,下一次的计算结果依赖于上一次的计算结果,反而言之即一个问题被拆解为若干子问题,在子问题的基础上获得问题的最优解。利用上公式会为数组序列形成一个数值矩阵,矩阵中的值即为c[i][j],当所有的c[i][j]都被赋值之后,在利用数组的路径回溯,即可找出最长公共子序列。
通过对最长公共子序列的理解,可以发现它们都可以借助二维数组进行辅助分析,它和最长公共子串的不同在于二维数组中的元素值的来源不一样,最长公共子序列来自于左上方、正上方、左方,而最长公共子串仅来自于左上方,这也是两者特征的最大区别:一个需要连续,一个可以不连续,这也使得计算最长公共子序列需要记录二维数组每个值的来源,以便于路径回溯。而最长公共子串则不需要记录二维数组数值来源,直接进行路径回溯。
3、实例:求两个字符串的子序列
对于给定的两个字符串“ABCDEF”和“ACEFHB”,计算它们的最长公共子序列,步骤如下:
(1)矩阵计算,按照最长公共子序列定义的公式,进行矩阵中的元素计算,得到下表所示矩阵。
![575ac94dedb5975c02ea3dcf423142e2.png](https://img-blog.csdnimg.cn/img_convert/575ac94dedb5975c02ea3dcf423142e2.png)
(2)路径标注。将矩阵中值的来源路径进行标注,如下表所示。
![f7fed261cba408f2f84a1de47f88563a.png](https://img-blog.csdnimg.cn/img_convert/f7fed261cba408f2f84a1de47f88563a.png)
(3)路径回溯,根据上表,从整个表格的右下角开始按照括号内的数值来源进行回溯,如下表灰色部分,即为回溯路径。
![fefb9d0d0d807099cb4ed80578fc8b68.png](https://img-blog.csdnimg.cn/img_convert/fefb9d0d0d807099cb4ed80578fc8b68.png)
将回溯的路径中的数值来源为“左上角”的字符依次取出,组成字符串“ACEF”,“ACEF”即为字符串“ABCDEF”和“ACEFHB”的最长公共子序列。
通过上述示例可以分析出,当从右下角考虑计算其值的过程时,每一次要么向上方或者向左、左上角取值,因此当字符串A的长度为m时,字符串B的长度为n时,至多调用m+n词就会遇到i=0或j=0的情况,相对而言,计算性能消耗较少,整体而言最长公共子序列的空间、时间复杂度为0(n2),可以通过优化使得空间复杂度达到O(n)。