查找两个字符串a,b中的最长公共子串
描述
查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
注:子串的定义:将一个字符串删去前缀和后缀(也可以不删)形成的字符串。请和“子序列”的概念分开!
本题含有多组输入数据!
数据范围:字符串长度,
进阶:时间复杂度:,空间复杂度:
输入描述:
输入两个字符串
输出描述:
返回重复出现的字符
示例1
输入:
abcdefghijklmnop
abcsafjklmnopqrstuvw
输出:
jklmnop
方法一:(暴力枚举)
#include<iostream>
#include<string>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
int n=str1.size(); int m=str2.size();
bool flag=false;
if(n<=m){
for(int len=n;len>0;--len){
for(int i=0;i<=n-len;++i){
for(int j=0;j<=m-len;++j){
if(str1.substr(i,len)==str2.substr(j,len)){
cout<<str1.substr(i,len)<<endl;
flag=true;
break;
}
}
if(flag) break;
}
if(flag) break;
}
}
else {
for(int len=m;len>0;--len){
for(int i=0;i<=m-len;++i){
for(int j=0;j<=n-len;++j){
if(str2.substr(i,len)==str1.substr(j,len)){
cout<<str2.substr(i,len)<<endl;
flag=true;
break;
}
}
if(flag) break;
}
if(flag) break;
}
}
}
return 0;
}
复杂度分析:
时间复杂度:
O(nm^ 3),其中m是较短字符串的长度,n是较长字符串的长度,枚举s1所有的子串需要O(m^2),find函数查找s2中是否含有子串需要O(mn)
空间复杂度:
O(1),无额外空间
方法二:(优化枚举)
具体做法:
其实找子串不用像方法一一样完全枚举,我们完全可以遍历两个字符串的所有字符串作为起始,然后同时开始检查字符是否相等,相等则不断后移,增加子串长度,如果不等说明以这两个为起点的子串截止了,不会再有了,后续比较长度维护最大值即可。
#include<iostream>
#include<string>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
int n=str1.length(); int m=str2.length();
string ans="";
if(n<=m){
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
int len=0;
int a=i,b=j;
while(str1[a]==str2[b] && a<n && b<m){
++a; ++b; ++len;
}
if(len>ans.length()){
ans=str1.substr(i,len);
}
}
}
}
else {
for(int i=0;i<m;++i){
for(int j=0;j<n;++j){
int len=0;
int b=j,a=i;
while(str1[b]==str2[a] && a<m && b<n){
++a; ++b; ++len;
}
if(len>ans.length()){
ans=str2.substr(i,len);
}
}
}
}
cout<<ans<<endl;
}
return 0;
}
把代码写的更简洁一点:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
if(str1.length()>str2.length()) swap(str1,str2);
int n=str1.length(); int m=str2.length();
string ans="";
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
int len=0;
int a=i,b=j;
while(str1[a]==str2[b] && a<n && b<m){
++a; ++b; ++len;
}
if(len>ans.length()){
ans=str1.substr(i,len);
}
}
}
cout<<ans<<endl;
}
return 0;
}
复杂度分析:
时间复杂度:
O(nm^2),其中m是较短字符串的长度,n是较长字符串的长度,分别枚举两个字符串每个字符作为起点,后续检查子串长度最坏需要花费O(m)
空间复杂度:
O(1),无额外空间
方法三:(动态规划)
具体做法:
动态规划继承自方法二,我们可以用dp[i][j]
表示在str1中以第i
个字符结尾在str2中以第j
个字符结尾时的公共子串长度,遍历两个字符串填充dp数组,在这个过程中比较维护最大值即可。
转移方程为:如果遍历到的该位两个字符相等,则此时长度等于两个前一位长度+1,
dp[i][j]=dp[i−1][j−1]+1
,如果遍历到该位时两个字符不相等,则置为0,因为这是子串,必须连续相等,断开要重新开始。
每次更新dp[i][j]
后,我们维护最大值即可找到最长子串。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
if(str1.length()>str2.length()) swap(str1,str2);
int n=str1.length(); int m=str2.length();
vector<vector<int>> dp(n+1,vector<int>(m+1,0));
//dp表示str1以第i个字符结尾的字符串和str2以第j个字符结尾的字符串的公共子字符串长度
//如果str1的第i个字符和str2的第j个字符相同,那么dp[i][j]=dp[i-1][j-1]+1,否则dp[i][j]置为0
int maxlen=0,end=0;
for(int i=1;i<n+1;++i){
for(int j=1;j<m+1;++j){
if(str1[i-1]==str2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}
else {
dp[i][j]=0;
}
if(maxlen<dp[i][j]){
maxlen=dp[i][j];
end=i-1;//记录最长子串结束下标
}
}
}
cout<<str1.substr(end-maxlen+1,maxlen)<<endl;
}
return 0;
}
复杂度分析:
时间复杂度:
O(mn),其中m是较短字符串的长度,n是较长字符串的长度,遍历两个字符串所有字符
空间复杂度:
O(mn),dp数组大小为m∗n