- 题目描述:
给定两个字符串,求它们前后重叠的最长子串的长度,比如”abcde”和“cdefg”是”cde”,长度为3。
- 输入:
输入可能包含多个测试案例。
对于每个测试案例只有一行, 包含两个字符串。字符串长度不超过1000000,仅包含字符’a’-‘z’。
- 输出:
对应每个测试案例,输出它们前后重叠的最长子串的长度。
- 样例输入:
abcde cdefg
样例输出:
3
思路:扩展KMP,用extend[i]保存 主串 S[i…..n-1]与 模式串 T的最长公共前缀的长度,其中n是S的长度。
然后扫描一遍 extend[] 如 extend[i] == n-i 那么这个后缀的长度就是我们要求的值。
关于扩展KMP,可以去看论文:《求最长回文子串与最长重复子串》何林 的集训队论文
代码:
#include <iostream>
#include <cstring>
#include <cstddef>
#include <cstdio>
using namespace std;
const int MAXN = 1000001;
int next[MAXN];
int extend[MAXN];
char S[MAXN],T[MAXN];
void getnext(char *T)
{
int k = 0;
int Tlen = strlen(T);
next[0] = Tlen;
while(k < Tlen-1 && T[k] == T[k+1])
k++;
next[1] = k;
k = 1;
for(int i = 2; i < Tlen; i++)
{
int p = k+next[k]-1, L = next[i-k]; // p :=已经匹配到的最远的位置
// L :=
if((i-1)+L >= p)
{
int j=(p-i+1)>0? p-i+1:0;
while(i+j < Tlen && T[k+j]==T[j])
j++;
next[i] = j;
k = i;
}
else next[k]=L;
}
}
void getextend(char *S,char *T)
{
int a = 0;
getnext(T);
int Slen = strlen(S);
int Tlen = strlen(T);
int len = Slen<Tlen ? Slen:Tlen;
while(a < len && S[a] == T[a]) a++;
extend[0] = a;
a = 0;
for(int i = 1; i < Slen; i++)
{
int p = a + extend[a]-1, L = next[i-a];
if(i+L-1 >= p)
{
int j = (p-i+1)>0? p-i+1:0;
while(i+j < Slen && j<Tlen && S[i+j] == T[j])
j++;
extend[i] = j;
a = i;
}
else extend[i] = L;
}
}
int main()
{
while(scanf("%s%s",&S,&T) != EOF)
{
getextend(S,T);
int n = strlen(S);
int res = 0;
for(int i = 0; i < n; i++)
{
if(extend[i] == n-i)
{
res = extend[i];
break;
}
}
cout<<res<<endl;
}
return 0;
}
下面介绍一种滚动哈希算法:假设要求S的后缀和T的前缀相等的最大长度,也可以利用滚动哈希在O(n+m)的时间内求解:
假设字符串C = c1c2....cm 定义哈希函数:
H(C) = (c1*b^(m-1)+c2*b^(m-2)+.....+cm*b^(0)) mod h ,
其中 b和h是两个互素的数,这样,我们就可以将字符串 S=s1s2...sn 从位置 k+1 开始长度为m的 子串 S[k+1....k+m]的哈希值
根据 子串 S[k....k+m-1]的哈希值在常数
时间内求出来:
H(S[k+1....k+m]) = H(S[k..k+m-1]*b - sk*b^m +s(k+m)) mod h ,
只要不断这样右移,就可以在O(n)的时间内求得所有位置的哈希值
#include <iostream>
#include <string>
#include <cstddef>
#include <cstdlib>
using namespace std;
typedef unsigned long long ull;
const ull B = 100000007;
int overlap(string &a,string &b)
{
int alen = a.size(), blen = b.size();
int res = 0;
ull ahash = 0, bhash = 0, t = 1;
for(int i = 1; i <= min(alen,blen); i++)
{
ahash = ahash + (a[alen-i]-'a')*t; //a的长度为i的后缀哈希值
bhash = bhash*B + b[i-1]-'a'; //b的长度为i的前缀哈希值
if(ahash == bhash) res = i;
t *= B;
}
return res;
}
int main()
{
string a,b;
while(cin>>a>>b)
{
cout<<overlap(a,b)<<endl;
}
return 0;
}
顺便说一下:不同字符串的哈希值发生冲突的概率非常低,通常可以忽略朴素的检查。