(字符串)最长公共子序列(Longest-Common-Subsequence,LCS)

问题:

最长公共子序列就是寻找两个给定序列的子序列,该子序列在两个序列中以相同的顺序出现,但是不必要是连续的。

例如序列X=ABCBDAB,Y=BDCABA。序列BCA是X和Y的一个公共子序列,但是不是X和Y的最长公共子序列,子序列BCBA是X和Y的一个LCS,序列BDAB也是。

思路:

1、最简单的方法就是暴力枚举。

先列举X所有的子序列,然后检查是否为Y的子序列,并记录最长的子序列。当该方法复杂度太高,假设X的长度为m,则X的子序列个数为2^m,指数级的复杂度是不实际的。

2、动态规划思想。

设X=<x1,x2,…,xm>和Y=<y1,y2,…,yn>为两个序列,LCS(Xm,Yn)表示以Xm结尾的字符串和以Yn结尾的字符串的一个最长公共子序列,可以看出

如果xm=yn,则LCS ( Xm,Yn ) = xm + LCS ( Xm-1,Yn-1 )。

如果xm!=yn,则LCS( Xm,Yn )= max{ LCS ( Xm-1, Yn ), LCS ( Xm, Yn-1 ) }

最长公共子序列长度:

状态转移方程:

初始状态:dp[i][j]=0 if i==0 || j==0

转移方程:dp[i][j] = dp[i-1][j-1] + 1  if (X[i-1]==Y[j-1])

     dp[i][j] = max ( dp[i-1][j], dp[i][j-1] )  if (X[i-1]!=Y[j-1])

最长公共子序列:

通过状态转移方程,可以逆推出最长子序列,如果x[i-1]==y[j-1] && dp[i][j]==dp[i-1][j-1]+1,则x[i-1]为最长子序列的元素,否则如果x[i-1]==y[j-1] && dp[i-1][j]>dp[i][j-1],则i--,否则j--,这样就得到一个倒序的最长子序列,具体见参考代码。

复杂度分析:

上述思路的时间复杂度为O(m*n),空间复杂度也为O(m*n);

dp[i][j] = dp[i-1][j-1] + 1  if (X[i-1]==Y[j-1])

dp[i][j] = max ( dp[i-1][j], dp[i][j-1] )  if (X[i-1]!=Y[j-1])

从状态转移方程可以看到,如果只求最长公共子序列长度的话,每一次转移的时候只与前一状态有关,因此空间复杂度可以从m*n降为2*n,只保存当前和前一状态,时间复杂度不变。

代码:

#include <iostream>
#include <vector>

using namespace std;

int LCS(char *str1,int len1,char *str2,int len2){
    // calculate length of LCS
    vector<vector<int> > dp(len1+1,vector<int>(len2+1,0));
    for(int i=0;i<=len1;i++){
        for(int j=0;j<=len2;j++){
            if(i==0 || j==0)
                dp[i][j]=0;
            else{
                if(str1[i-1]==str2[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
    }
    // record the LCS
    int len=dp[len1][len2];
    char lcsArr[len];
    lcsArr[len]=‘\0‘;
    int i=len1,j=len2;
    while(i && j){
        if(str1[i-1]==str2[j-1] && dp[i][j]==dp[i-1][j-1]+1){
            lcsArr[--len]=str1[i-1];
            i--;
            j--;
            }
        else if(str1[i-1]!=str2[j-1] && dp[i-1][j]>dp[i][j-1])
            i--;
        else
            j--;
    }

    cout<<"Length of LCS is: "<<len<<endl;
    cout<<"SubSequency of LCS is: "<<lcsArr<<endl;

    return dp[len1][len2];
}

int main()
{
    char str1[]="abcd";
    char str2[]="bd";
    int len1=sizeof(str1)/sizeof(str1[0])-1;
    int len2=sizeof(str2)/sizeof(str2[0])-1;
    cout << LCS(str1,len1,str2,len2) << endl;
    return 0;
}
int LCS2(char *str1,int len1,char *str2,int len2){
    // only to calculate length of LCS
    // reduce the space complexity from m*n to 2*n
    vector<vector<int> > dp(2,vector<int>(len2+1,0));
    int k;
    for(int i=0;i<=len1;i++){
        k=i&1;
        for(int j=0;j<=len2;j++){
            if(j==0)
                dp[k][j]=0;
            else{
                if(str1[i-1]==str2[j-1])
                    dp[k][j]=dp[1-k][j-1]+1;
                else
                    dp[k][j]=max(dp[1-k][j],dp[k][j-1]);
            }
        }
    }
    cout<<"Length of LCS is: "<<dp[k][len2]<<endl;

    return dp[k][len2];
}

运行结果:

时间: 2024-10-06 12:29:02

(字符串)最长公共子序列(Longest-Common-Subsequence,LCS)的相关文章

UVA10100:Longest Match(最长公共子序列)&amp;&amp;HDU1458Common Subsequence ( LCS)

题目链接:http://blog.csdn.net/u014361775/article/details/42873875 题目解析: 给定两行字符串序列,输出它们之间最大公共子单词的个数 对于给的两个序列X 和 Y,用i 和 j分别作为它们的前缀指针,f[i][j]表示序列X的前缀Xi 和 序列Y的前缀Yi 的最长公共子序列的长度,在这道题中,可把每一个单词当作一个字符来进行比较. 当 i | j 为0时 ,此 f[i][j] = 0; 当 i!=0 && j!=0 &&

最长公共子串(Longest common substring)

问题描述: 给定两个序列 X=<x1, x2, ..., xm>, Y<y1, y2, ..., yn>,求X和Y长度最长的公共子串.(子串中的字符要求连续) 这道题和最长公共子序列(Longest common subsequence)很像,也可以用动态规划定义.公式如下: 这里c[i,j]表示以Xi,Yj结尾的最长公共子串的长度. 程序实现: int longestCommonSubstring(string x, string y) { int m = x.length();

动态规划 ---- 最长公共子序列(Longest Common Subsequence, LCS)

分析: 完整代码: // 最长公共子序列 #include <stdio.h> #include <algorithm> using namespace std; const int N = 100; char A[N], B[N]; int dp[N][N]; int main() { freopen("in.txt", "r", stdin); int n; gets(A + 1); // 从下标1开始读入 gets(B + 1); in

动态规划(二)最长公共子串(Longest Common Substring)

题目描述: 求两个输入序列的最长的公共子字符串的长度.子字符串中的所有字符在源字符串中必须相邻. 如字符串:21232523311324和字符串312123223445,他们的最长公共子字符串为21232,长度为5. 最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为: 子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列:也就是说,子串中字符的位置必须

利用后缀数组(suffix array)求最长公共子串(longest common substring)

摘要:本文讨论了最长公共子串的的相关算法的时间复杂度,然后在后缀数组的基础上提出了一个时间复杂度为o(n^2*logn),空间复杂度为o(n)的算法.该算法虽然不及动态规划和后缀树算法的复杂度低,但其重要的优势在于可以编码简单,代码易于理解,适合快速实现. 首先,来说明一下,LCS通常指的是公共最长子序列(Longest Common Subsequence,名称来源参见<算法导论>原书第3版p223),而不是公共最长子串(也称为最长公共子串). 最长公共子串问题是在文本串.模式串中寻找共有的

实习生面试--算法题之字符串最长公共子序列长度

题目:求两字符串的最长公共子序列的长度. 题外话:最长公共子串,子序列问题是被充分讨论的问题,网上一搜一大把,请bing之. 本题只要求求最长公共子序列的长度,而不需要记录最长的公共子序列,给予了我们便利,请参考代码: 1 int max(int a, int b) 2 { 3 return a > b ? a : b; 4 } 5 6 int lcs(char* str1, char* str2) 7 { 8 if (str1 == nullptr || str2 == nullptr) 9

51Nod 1092 回文字符串 | 最长公共子序列变形

求字符串和其逆的最长公共子序列,需要添加的字符数就为长度-最长公共子序列长 #include "stdio.h" #include "string.h" #define maxn 1005 char s[maxn],s1[maxn]; int dp[maxn][maxn]; int main() { int n=0,i,j,len; scanf("%s",s); len=strlen(s); strcpy(s1,s); strrev(s1); f

字符串最长公共子序列问题

找两个字符串的最长公共子序列,最长公共子序列并不要求连续. 代码如下: package string; import java.util.ArrayList; import java.util.List; /** * 字符串的最长公共子序列问题 * @author Administrator * */ public class LCSequence { /** * 求最长公共子序列长度 * @param s1 * @param s2 * @return */ public int getMaxL

Longest Common Subsequence (LCS)

最长公共子序列(LCS)是经典的DP问题,求序列a[1...n], b[1..m]的LCS. 状态是DP[i][j],表示a[1..i],b[1..j]的LCS. DP转移方程是 DP[i][j]= DP[i-1][j-1]+1, a[i] == b[j] max{ DP[i][j-1], DP[i-1][j] }, a[i] != b[i]  ----------------------------------------------------------------------------