欢迎访问https://blog.csdn.net/lxt_Lucia~~

宇宙第一小仙女\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~

--------------------------------我只是一条可爱哒分界线-------------------------------

1.摘要:

继上篇最长上升子序列后,本篇主要讲述最长公共子序列 (LCS) 。

2.LCS定义:

最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。

如果觉得抽象不好理解,那么咱们还是采用学习LIS的时候的方式。首先,让我们先来看一下子串子序列还有公共子序列的概念(在上篇LIS中也曾涉及过) ,我们以字符子串和字符子序列为例,更为形象,也能顺带着理解字符的子串和子序列:

(1)字符子串:指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。

(2)字符子序列:指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。

 (3)  公共子序列:如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序列A和序列B的公共子序列。如对序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 来说,序列1,8,7是它们的一个公共子序列。

那么现在,我们再通俗的总结一下最长公共子序列(LCS):就是A和B的公共子序列中长度最长的(包含元素最多的)
仍然用序列1,3,5,4,2,6,8,7和序列1,4,8,6,7,5为例,它们的最长公共子序列有1,4,8,7和1,4,6,7两种,但最长公共子序列的长度是4。由此可见,最长公共子序列(LCS)也不一定唯一

请大家用集合的观点来理解这些概念,子序列、公共子序列以及最长公共子序列都不唯一,所以我们通常特判取一个最长公共子序列,但很显然,对于固定的两个数组,虽然最LCS不一定唯一,但LCS的长度是一定的。查找最长公共子序列与查找最长公共子串的问题不同的地方在于:子序列不需要在原序列中占用连续的位置。最长公共子串(要求连续)和最长公共子序列是不同的。

那么该如何求出两个序列的最长公共子序列长度呢?请继续往下看~

3.LCS长度求法:

你首先能想到的恐怕是暴力枚举?那我们先来看看:序列A有 2^n 个子序列,序列B有 2^m 个子序列,如果任意两个子序列一一比较,比较的子序列高达 2^(n+m) 对,这还没有算具体比较的复杂度。或许你说,只有长度相同的子序列才会真正进行比较。那么忽略空序列,我们来看看:对于A长度为1的子序列有C(n,1)个,长度为2的子序列有C(n,2)个,……长度为n的子序列有C(n,n)个。对于B也可以做类似分析,即使只对序列A和序列B长度相同的子序列做比较,那么总的比较次数高达:C(n,1)*C(m,1)*1 + C(n,2) * C(m,2) * 2+ …+C(n,p) * C(m,p)*p,其中p = min(m, n)。

吓着了吧?怎么办?我们试试使用动态规划算法

我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示Ax和By的一个最长公共子序列。让我们来看看如何求LCS(x, y)。我们令x表示子序列考虑最后一项

(1) Ax = By

那么它们L(Ax, By)的最后一项一定是这个元素!

为什么呢?为了方便,我们令t = Ax = By, 我们用反证法:假设L(x,y)最后一项不是t,则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是Aa=Bb ≠ t, 且显然有a < x, b < y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!
       如果我们从序列Ax中删掉最后一项ax得到Ax-1,从序列By中也删掉最后一项by得到By-1,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – 1, y - 1)。为什么呢?和上面的道理相同,如果得到的序列不是L(x - 1, y - 1),则它一定比L(x - 1, y - 1)短(注意L(,)是个集合!),那么它后面接上元素t得到的子序列L(x,y)也比L(x - 1, y - 1)接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。因此L(x, y) = L(x - 1, y - 1) 最后接上元素t,LCS(Ax, By) = LCS(x - 1, y - 1) + 1。

(2)  Ax ≠ By

        仍然设t = L(Ax, By), 或者L(Ax, By)是空序列(这时t是未定义值不等于任何值)。则t  ≠ Ax和t  ≠ By至少有一个成立,因为t不能同时等于两个不同的值嘛!

(2.1)如果t  ≠ Ax,则有L(x, y)= L(x - 1, y),因为根本没Ax的事嘛。

LCS(x,y) = LCS(x – 1, y)
(2.2)如果t  ≠ By,l类似L(x, y)= L(x , y - 1)

LCS(x,y) = LCS(x, y – 1)
       可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y) = max(LCS(x – 1, y) , LCS(x, y – 1))。看看目前我们已经得到了什么结论:

LCS(x,y) = 
(1) LCS(x - 1,y - 1) + 1      (Ax = By)
(2) max(LCS(x – 1, y) , LCS(x, y – 1))    (Ax ≠ By)

这时一个显然的递推式,光有递推可不行,初值是什么呢?显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:

LCS(x,y) = 
(1) LCS(x - 1,y - 1) + 1 如果Ax = By
(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
(3) 0 如果x = 0或者y = 0

到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + 1)行(m + 1)列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(,)。

大概的伪代码如下:
输入序列A, B长度分别为n,m,计算二维表 LCS(int,int):

for x = 0 to n dofor y = 0 to m doif (x == 0 || y == 0) then LCS(x, y) = 0else if (Ax == By) thenLCS(x, y) =  LCS(x - 1,y - 1) + 1else LCS(x, y) = ) max(LCS(x – 1, y) , LCS(x, y – 1))endifendfor
endfor

注意: 我们这里使用了循环计算表格里的元素值,而不是递归,如果使用递归需要已经记录计算过的元素,防止子问题被重复计算。

现在问题来了,我们如何得到一个最长公共子序列而仅仅不是简单的长度呢?其实我们离真正的答案只有一步之遥!

仍然考虑那个递推式,我们LCS(x,y)的值来源的三种情况:

(1) LCS(x – 1,  y – 1) + 1如果Ax = By
这对应L(x,y) = L(x,- 1 y- 1)末尾接上Ax

(2.1) LCS(x – 1, y)  如果Ax ≠ By且LCS(x – 1, y) ≥LCS(x, y – 1)
这对应L(x,y)= L(x – 1, y)
(2.2) LCS(x, y – 1)  如果Ax ≠ By且LCS(x – 1, y) <LCS(x, y – 1)
这对应L(x,y) = L(x, y – 1)

(3) 0 如果 x =0或者y = 0
这对应L(x,y)=空序列

注意(2.1)和(2.2) ,当LCS(x – 1, y) = LCS(x, y – 1)时,其实走哪个分支都一样,虽然长度时一样的,但是可能对应不同的子序列,所以最长公共子序列并不唯一。
神奇吧?又一个类似的递推公式。可见我们在计算长度LCS(x,y)的时候只要多记录一些信息,就可以利用这些信息恢复出一个最长公共子序列来。就好比我们在迷宫里走路,走到每个位置的时候记录下我们时从哪个方向来的,就可以从终点回到起点一样。

另外,说一下复杂度?时间复杂度时O(n * m),空间也是O(n * m)。

4.LCS经典例题模板:

例1:Common Subsequence(求LCS长度)

Description

A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is a subsequence of X if there exists a strictly increasing sequence <i1, i2, ..., ik> of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y. 
The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.

Sample Input

abcfbc abfcab
programming contest
abcd mnp

Sample Output

4
2
0

思路:

题意是,称序列 Z = < z1, z2, ..., zk >是序列X = < x1, x2, ..., xm >的子序列当且仅当存在严格上升的序列< i1, i2, ..., ik >,使得对j = 1, 2, ... ,k, 有xij = zj。比如Z = < a, b, f, c > 是X = < a, b,c, f, b, c >的子序列。现在给出两个序列X 和Y,你的任务是找到X 和Y 的最大公共子序列,也就是说要找到一个最长的序列Z,使得Z 既是X 的子序列也是Y 的子序列。

其实就是模板题啦~求LCS长度,当A[i]=A[j]时d(I,j)d(i-1,j-1)+1,否则d(i,j)=max{ d(i-1,j),d(i,j-1) },时间复杂度为O(n*m),其中n和m分别是序列A和B的长度。

代码:

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>using namespace std;char a[1001], b[1001];
int dp[1001][1001], len1, len2;void lcs(int i,int j)
{for(i=1; i<=len1; i++){for(j=1; j<=len2; j++){if(a[i-1] == b[j-1])dp[i][j] = dp[i-1][j-1] + 1;else if(dp[i-1][j] > dp[i][j-1])dp[i][j] = dp[i-1][j];elsedp[i][j] = dp[i][j-1];}}
}int main()
{while(~scanf(" %s",a)){scanf(" %s", b);memset(dp, 0, sizeof(dp));len1 = strlen(a);len2 = strlen(b);lcs(len1, len2);printf("%d\n", dp[len1][len2]);}return 0;
}

例2:最长公共子序列Lcs(求LCS具体是什么)

Description

给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的)。

比如两个串为:abcicba 和 abdkscab,则 ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列。

Input

第1行:字符串A 
第2行:字符串B 
(A,B的长度 <= 1000)

Output

输出最长的子序列,如果有多个,随意输出1个。

Sample Input

abcicba
abdkscab

Sample Output

abca

思路:

此题的切入点就是动态规划,通过动归来确定哪些字符是最长公共子序列中的字符,mat[i][j] 表示第一个序列的前i个字符和第二个序列的前j个字符的公共子序列,动态转移方程为:

dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值,

(1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列,

(2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列,

(3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入状态为1,否则为0。

然后根据动归的状态,来判断我们要求得的序列中的字符有哪些。

代码:

#include<queue>
#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3fusing namespace std;char a[1001], b[1001];
int dp[1001][1001], len1, len2;void lcs(int i, int j)
{for(i=1; i<=len1; i++){for(j=1; j<=len2; j++){if(a[i-1] == b[j-1])dp[i][j] = dp[i-1][j-1] + 1;else if(dp[i-1][j] > dp[i][j-1])dp[i][j] = dp[i-1][j];elsedp[i][j] = dp[i][j-1];}}
}void llcs()
{int i, j, z = 0;char c[1001];memset(c, 0, sizeof(c));i = len1, j = len2;while(i!=0 && j!=0){if(a[i-1] == b[j-1]){c[z++] = a[--i];j--;}else if(dp[i-1][j] < dp[i][j-1])j--;else if(dp[i][j-1] <= dp[i-1][j])i--;}for(i=z-1; i>=0; i--)printf("%c", c[i]);printf("\n");}int main()
{while(~scanf(" %s", a)){scanf(" %s", b);memset(dp, 0, sizeof(dp));len1 = strlen(a);len2 = strlen(b);lcs(len1, len2);llcs();}return 0;
}

例3:Advanced Fruits(根据LCS将两个词拼接)

Decription

The company "21st Century Fruits" has specialized in creating new sorts of fruits by transferring genes from one fruit into the genome of another one. Most times this method doesn't work, but sometimes, in very rare cases, a new fruit emerges that tastes like a mixture between both of them. 
A big topic of discussion inside the company is "How should the new creations be called?" A mixture between an apple and a pear could be called an apple-pear, of course, but this doesn't sound very interesting. The boss finally decides to use the shortest string that contains both names of the original fruits as sub-strings as the new name. For instance, "applear" contains "apple" and "pear" (APPLEar and apPlEAR), and there is no shorter string that has the same property. 
A combination of a cranberry and a boysenberry would therefore be called a "boysecranberry" or a "craboysenberry", for example. 
Your job is to write a program that computes such a shortest name for a combination of two given fruits. Your algorithm should be efficient, otherwise it is unlikely that it will execute in the alloted time for long fruit names.

Input

Each line of the input contains two strings that represent the names of the fruits that should be combined. All names have a maximum length of 100 and only consist of alphabetic characters. 
Input is terminated by end of file.

Output

For each test case, output the shortest name of the resulting fruit on one line. If more than one shortest name is possible, any one is acceptable.

Sample Input

apple peach
ananas banana
pear peach

Sample Output

appleach
bananas
pearch

思路:

在LCS的基础之上加上路径记录,生成dp数组的时候做上标记,之后按顺序输出结果字符串。注意还要考虑一下没有公共子序列的情况。

代码:

#include<queue>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>using namespace std;char a[1001], b[1001], s[10000];
int dp[1001][1001], len1, len2, k = 0;void lcs(int i,int j)
{for(i=1; i<=len1; i++){for(j=1; j<=len2; j++){if(a[i-1] == b[j-1])dp[i][j] = dp[i-1][j-1] + 1;else if(dp[i-1][j] > dp[i][j-1])dp[i][j] = dp[i-1][j];elsedp[i][j] = dp[i][j-1];}}
}void llcs()
{int i, j, z = 0, k = 0;i = len1, j = len2;while(dp[i][j]){if(a[i-1] == b[j-1]){s[k++]=a[--i];j--;}else if(dp[i][j-1] < dp[i-1][j])s[k++] = a[--i];else if(dp[i][j-1] >= dp[i-1][j])s[k++] = b[--j];}while(i != 0)s[k++] = a[--i];while(j!=0)s[k++] = b[--j];for(z=k-1; z>=0; z--)printf("%c", s[z]);printf("\n");
}int main()
{while(~scanf(" %s",a)){scanf(" %s", b);memset(dp, 0, sizeof(dp));len1 = strlen(a);len2 = strlen(b);lcs(len1, len2);llcs();}return 0;
}

5.相关知识:( 建议放在一起比较区分 )

1)最长上升子序列  ( LIS )  戳这里~

2)最长回文子串 and 最长回文子序列  (LPS)  戳这里 ~

模板都在这儿呢~ 快去AC几发叭~

--------------------------------我也是有底线的---------------------------------

宇宙第一小仙女\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~

参考资料:https://blog.csdn.net/lz161530245/article/details/76943991

最长公共子序列 (LCS) 详解+例题模板(全)相关推荐

  1. 最长上升子序列 (LIS) 详解+例题模板 (全)

    欢迎访问https://blog.csdn.net/lxt_Lucia-- 宇宙第一小仙女\(^o^)/-萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗- ------------ ...

  2. 尺取法 — 详解 + 例题模板(全)

    尺取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案.尺取法比直接暴力枚举区间效率高很多 ...

  3. 动态规划算法解最长公共子序列LCS问题

    动态规划算法解LCS问题 作者 July 二零一零年十二月三十一日 本文参考:微软面试100题系列V0.1版第19.56题.算法导论.维基百科. 第一部分.什么是动态规划算法 ok,咱们先来了解下什么 ...

  4. 动态规划之最长公共子序列(LCS)

    最长公共子序列(LCS,Longest Common Subsequence).其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最 ...

  5. 程序员编程艺术第十一章:最长公共子序列(LCS)问题

    程序员编程艺术第十一章:最长公共子序列(LCS)问题 0.前言 程序员编程艺术系列重新开始创作了(前十章,请参考程序员编程艺术第一~十章集锦与总结).回顾之前的前十章,有些代码是值得商榷的,因当时的代 ...

  6. 算法之最长公共子序列(LCS)问题

    算法课上老师留的作业,最长公共子序列LCS(Longest Common Subsequence)问题,首先看到这个问题感觉有点复杂,和最长公共子串不同,公共子序列并不要求元素相邻,看起来只有穷举才能 ...

  7. 最长公共子序列 - LCS

    最长公共子序列 - LCS 问题描述 子序列定义 子串定义 公共子序列定义 最长公共子序列(以下简称LCS) 动态规划解决 子问题划分及依赖关系 递推公式 伪代码 代码实现 复杂度分析 问题描述 子序 ...

  8. 最长公共子序列php,动态规划(最长公共子序列LCS)

    概念 求解决策过程最优化的结果 (可能有多个) 把多阶段过程转化为一系列单阶段过程,利用各阶段之间的关系,逐个求解 计算过程中会把结果都记录下,最终结果在记录中找到. 举例 求两个字符串的最长公共子序 ...

  9. python实现求解最长公共子序列LCS问题

    在实现论文<Automatically Generating Models for Botnet Detection>论文的算法中,用到了一个The longest commom subs ...

最新文章

  1. 结合脑成像技术与人工智能,破除自杀的“诅咒”
  2. python自学多久可以找到工作-25岁从零开始学习python还能找到工作吗?
  3. 台达b2伺服说明书_三菱Q系列定位模块及伺服参数不会设置?看这一篇就够了!...
  4. python tcp通信如何实现多人聊天,Python实现多用户全双工聊天(一对一),python多用户,多用户全双工聊天简陋...
  5. mysql 组复制 不一致_使用MySQL组复制的限制和局限性
  6. 5G +边缘计算,优酷如何做云渲染?
  7. jQuery 中json字符串与对象互转
  8. python获取系统内存占用信息的实例方法
  9. django传递URL到某个app的urls.py文件中
  10. angular实例模态【modal】
  11. 【转】linux常用命令:find、grep
  12. 【pandas】读取大型文件技巧
  13. ASP.NET中用healthMonitor属性用法
  14. 五款PC端小说阅读器 readbook、非常酷阅读器、iSilo、AlReader、haalireader
  15. postman安装报错 无法定位_接口测试工具postman安装及使用
  16. Mac Air 配置Android开发环境
  17. 苹果系统中英文输入法切换_苹果输入法怎么切换_苹果系统如何切换输入法-win7之家...
  18. rap格式鸿蒙,你,想要成为rap star吗?
  19. 饿了么官宣合作抖音后,美团的失意是什么?
  20. 苹果手机升级13无法开机_苹果手机升级之后开不了机怎么办?

热门文章

  1. 第三节:整型数据和实型数据
  2. 1_Univariate_plotting_with_pandas
  3. delete空间释放具体细节
  4. JAVA锁定mdb文件_ArcGIS中有关MDB文件锁定的说明
  5. Matlab扫频法求系统传递函数
  6. 个人博客上线了!!!(官宣
  7. 中国人为什么不富裕?
  8. 夜雨数竞笔记-不定积分(7)-待定系数法
  9. android studio3.6.3编译Telegram android 1482 问题小记(nebula chatengine 指定版本)
  10. 微信公众号发送小程序卡片_小程序、公众号客服消息可以发送小程序卡片啦