LCS之最长公共子串

    最长公共子串(Longest Common Substring)是指几个字符串之间的公共子串中长度最大的一个。它可以说是最长公共子序列的一种特例,同样可以用动态规划的思想来解决,不同点就在于求最长公共子序列时我们只需要把光标向后移,而不用去关心它移动了多少位,但在求最长公共子串的时候,我们就需要关心光标后移的个数了。我们仍然拿  求两个字符串str1和str2之间的最长公共子串  来讨论:

    首先定义一个二维数组dp,和最长公共子序列不同的是,这里的dp(i,j)并不表示str1中前i个元素的子串和str2中前j个元素的子串之间的最长公共子串的大小,单独的某一个的dp值是没有意义的,有意义的是整个dp数组中的最大值,这个最大值表示两个字符串之间的最长公共子串的大小,至于为什么要这样做,我们知道,由于要求的是连续的子序列,所以每一次只能拿当前状态dp(i,j)和上一个状态dp(i-1,j-1)来比较,如果str1[ i-1 ]=str2[ j-1 ],那么dp(i,j)=dp(i-1,j-1)+1;如果str1[ i-1 ]!=str2[ j-1 ],由于我们只需要知道dp数组中的最大值,而不去关心具体某一个dp值,这样我们就可以不用纠结dp(i,j)和dp(i-1,j),dp(i,j-1)之间的关系,直接令dp(i,j)=0就行了,因为str1[ i-1 ]是不等于str2[ j-1 ]的,这样光标再往后移时子序列已经不连续了,如果非要拿dp(i,j)来纪录 str1中前i个元素的子串和str2中前j个元素的子串之间的最长公共子串的大小 ,那么无非就是令dp(i,j)=max( dp(i-1,j),dp(i,j-1) ),显而易见,dp值是不会增加的,所以我们完全没必要这么做,倒不如直接令dp(i,j)=0来的划算。

    这样一来状态转移方程就变为了:

(1)str1[ i-1 ]=str2[ j-1 ]时,dp(i,j)=dp(i-1,j-1)+1;

(2)str1[ i-1 ]!=str2[ j-1 ]时,dp(i,j)=0;


实现代码如下:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define maxn 100
void solve_LCS(string str1,string str2)
{
    int dp[maxn][maxn]={0};//dp[][]中的最大值即为最长公共子串的长度
    int lcs=0,s;
    for(int i=1;i<=str1.length();i++)
      for(int j=1;j<=str2.length();j++)
      {
          if(str1[i-1]==str2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
          else dp[i][j]=0;
          if(lcs<dp[i][j])
          {
              lcs=dp[i][j];//纪录dp[][]中的最大值
              s=i;//纪录最长公共子串的末端在str1中的位置(也可以纪录在str2中的位置)
          }
      }
    cout<<"最长公共子串的长度为:"<<lcs<<endl;
    char p[maxn];//纪录子串
    p[lcs--]='\0';
    for(int i=s-1;lcs>=0;i--)
      p[lcs--]=str1[i];
    cout<<p<<endl;//输出子串
}
int main()
{
    string str1,str2;
    while(cin>>str1>>str2)
      solve_LCS(str1,str2);
    return 0;
}



    另外求最长公共子串还有一种思路,算法大致是这样的:

    我们把str1固定,然后让str2从str1的最右边开始向左移动,每左移一次,我们就找出两个字符串重合的部分的最长公共子串的大小,然后纪录这些值中的最大值。

实现代码如下:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define maxn 100
void solve_LCS(string str1,string str2)
{
    int lcs=0;//纪录最长公共子串的大小
    int s;//纪录最长公共子串在str1中的位置
    int s1,s2;//纪录光标在两字符串中的位置
    for(int i=0;i<str1.length()+str2.length();i++)
    {
        s1=s2=0;
        if(i<str1.length()) s1=str1.length()-i;
        else s2=i-str1.length();
        int j,cnt=0;//纪录当前的公共子串的大小
        for(j=0;s1+j<str1.length()&&s2+j<str2.length();j++)
        {
            if(str1[ s1+j ]==str2[ s2+j ]) cnt++;
            else
            {
                if(cnt>lcs)
                {
                    lcs=cnt;
                    s=s1+j;
                }
                cnt=0;
            }
        }
        if(cnt>lcs)
        {
            lcs=cnt;
            s=s1+j;
        }
    }
    cout<<"最长公共子串的长度为:"<<lcs<<endl;
    char p[maxn];//纪录子串
    p[lcs--]='\0';
    for(int i=s-1;lcs>=0;i--)
      p[lcs--]=str1[i];
    cout<<p<<endl;//输出子串
}
int main()
{
    string str1,str2;
    while(cin>>str1>>str2)
      solve_LCS(str1,str2);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值