最长公共子串(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;
}