最长公共子序列问题

Time Limit: 1000 ms Memory Limit: 65536 KiB

Submit Statistic

Problem Description

给定两个序列 X={x1,x2,…,xm} 和 Y={y1,y2,…,yn},找出X和Y的最长公共子序列。

Input

输入数据有多组,每组有两行 ,每行为一个长度不超过500的字符串(输入全是大写英文字母(A,Z)),表示序列X和Y。

Output

每组输出一行,表示所求得的最长公共子序列的长度,若不存在公共子序列,则输出0。

Sample Input

ABCBDAB
BDCABA

Sample Output

4

Hint

Source

一.最长公共子序列问题(LCS问题)

 

  • 给定两个字符串A和B,长度分别为m和n,要求找出它们最长的公共子序列,并返回其长度。例如:
  •   A = "HelloWorld"
  •     B = "loop"
  • 则A与B的最长公共子序列为 "loo",返回的长度为3。此处只给出动态规划的解法:
  • 定义子问题dp[i][j]为字符串A的第一个字符到第 i 个字符串和字符串B的第一个字符到第 个字符的最长公共子序列,
  • Aapp”,B“apple”dp[2][3]表示 “ap” 和 “app” 的最长公共字串
  • 注意到代码中 dp 的大小为 (n + 1) x (m + 1) ,这多出来的一行和一列是第 行和第 列,初始化为 0
  • 表示空字符串和另一字符串的子串的最长公共子序列,例如dp[0][3]表示  "" 和 “app” 的最长公共子串。
  • 当我们要求dp[i][j],我们要先判断A的第i个元素B的第j个元素是否相同即判断A[i - 1]和 B[j -1]是否相同,
  • 如果相同它就是dp[i-1][j-1]+ 1,相当于在两个字符串都去掉一个字符时的最长公共子序列再加 1
  • 否则最长公共子序列dp[i][j - 1] dp[i - 1][j]中大者。所以整个问题的初始状态为:

相应的状态转移方程为: 

 

 该算法的时间复杂度为O(n*m),空间复杂度为O(n*m)。此外,由于遍历时是从下标1开始的,因为下标为0表示空字符串;所以第A的第i个字符实际上为A[i -1],B的第j个字符为B[j-1]

#include <bits/stdc++.h>
using namespace std;
const int maxn=501;
int main()
{
    char s1[maxn];
    char s2[maxn];
    while(scanf("%s",s1)!=EOF)
    {
        scanf("%s",s2);
        int m=strlen(s1);
        int n=strlen(s2);
        int dp[m+1][n+1];
        for(int i=0; i<=m; i++)
            dp[i][0]=0;
        for(int i=0; i<=n; i++)
            dp[0][i]=0;
        for(int i=1; i<=m; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(s1[i-1]==s2[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
        printf("%d\n",dp[m][n]);
    }
    return 0;
}

 

二.最长公共子串问题

 

  • 给定两个字符串A和B,长度分别为m和n,要求找出它们最长的公共子串,并返回其长度。例如:
  •   A = "HelloWorld"
  •     B = "loop"
  • 则A与B的最长公共子串为 "lo",返回的长度为2。我们可以看到子序列和子串的区别:
  • 子序列和子串都是字符集合的子集,但是子序列不一定连续,但是子串一定是连续的
  • 同样地,这里只给出动态规划的解法:定义dp[i][j]表示以A中第i个字符结尾的子串和B中第j个字符结尾的子串的的最大公共子串(公共子串实际上指的是这两个子串的所有部分)的长度(要注意这里和LCS的不同,
  • LCS中的dp[i+1][j+1]一定是大于等于dp[i][j]的;但最长公共子串问题就不一定了,它的dp[i][j]表示的子串不一定是以A[0]开头B[0]开头的,但是一定是以A[i-1]、B[j-1]结尾的),
  • 同样地, dp 的大小也为 (n + 1) x (m + 1) ,这多出来的一行和一列是第 行和第 列,初始化为 0,表示空字符串和另一字符串的子串的最长公共子串。
  • 当我们要求dp[i][j],我们要先判断A的第i个元素B的第j个元素是否相同即判断A[i - 1]和 B[j -1]是否相同,
  • 如果相同它就是dp[i - 1][j- 1] + 1,相当于在两个字符串都去掉一个字符时的最长公共子串再加 1;否则最长公共子串取0。所以整个问题的初始状态为:

 相应的状态转移方程为:

 

#include <bits/stdc++.h>
using namespace std;
const int maxn=501;
int main()
{
    char s1[maxn];
    char s2[maxn];
    while(scanf("%s",s1)!=EOF)
    {
        int len=0;
        scanf("%s",s2);
        int m=strlen(s1);
        int n=strlen(s2);
        int dp[m+1][n+1];
        for(int i=0; i<=m; i++)
            dp[i][0]=0;
        for(int i=0; i<=n; i++)
            dp[0][i]=0;
        for(int i=1; i<=m; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(s1[i-1]==s2[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                    len=max(len,dp[i][j]);
                }
                else dp[i][j]=0;
            }
        }
        printf("%d\n",len);
    }
    return 0;
}

 该算法的时间复杂度为O(n*m),空间复杂度为O(n*m)。同样地,遍历下标也是从1开始的。不过关于最长公共子串问题,有几点需要注意下:

  • 1.由于dp[i][j]不像LCS是个递增的数组,所以它在每次更新时需要同时更新最大值rs,且最后返回的结果是rs。而LCS中返回的直接就是dp[n][m]。
  • 2.从代码上来看,两者的结构其实差不多,只不过状态转移方程有些小许的不同,分析过程也类似。
  • 3.另外,关于这量两种问题还有更优的解法,不过本文主要是DP的思想去解决,当然其中还有对DP的优化
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值