题目来源: Codility
基准时间限制:1 秒 空间限制:131072 KB 分值: 160 难度:6级算法题
收藏
取消关注
给定一个字符串S,找到另外一个字符串T,T既是S的前缀,也是S的后缀,并且在中间某个地方也出现一次,并且这三次出现不重合。求T最长的长度。
例如:S = "abababababa",其中"aba"既是S的前缀,也是S的后缀,中间还出现了一次,并且同前缀后缀均不重合。所以输出"aba"的长度3。如果找不到一个符合条件的字符,输出0。
Input
输入字符串S。(1 <= L <= 1000000, L为S的长度)
Output
输出这个最长的子串的长度。
Input示例
abababababa
Output示例
3
想法:
非常有意思的一道字符串题目,题面很了然,但这道题难度还是不小的。字符串str的长度高达1 000 000,只能想O(n)的做法了。
定义suffix(i)为后缀i, 即suffix(i) = str[i]str[i+1]str[i+1]...str[n-1]
定义d[i]为suffix(i)与str的最长公共前缀
假设已经算出了d[1], d[2], ... , d[i-1](d[0]没必要计算)。定义r为max{d[j] + j - 1 | 1 <= j <= i-1},即曾经匹配的最右边界。同时定义l为取得最右边界r的j。l和r初始化为-1。现在利用这些信息计算d[i]
当 i <= r
因为有str[l ... r] == str[0 ... r-l]
所以str[i ... r] == str[ii ... r-l] (令ii = i - l)
d[ii]已经求得,下面利用d[ii]快速计算d[i]
如果i + d[ii] - 1 < r,易知d[i] = d[ii]
否则,d[i] >= r - i, 可令d[i] = r - i,然后从i + d[i]开始暴力比较即可
当i > r
暴力比较
其中,r和l在可更新的时候进行更新即可。
关于时间复杂度 : 从上面可以看出,对于每一次的考虑,要么r增大1,要么比较一次,出现了不匹配,退出。总体上复杂度为O(n)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000 + 10;
int d[N];
char str[N];
void init(int n)
{
int l = -1, r = -1;
for(int i = 1; i < n; ++i)
{
if(i <= r)
{
int ni = i - l;
if(i + d[ni] - 1 < r) d[i] = d[ni];
else
{
d[i] = r - i;
while(i + d[i] < n && str[d[i]] == str[i + d[i]])
{
d[i]++;
if(i + d[i] - 1 > r)
{
r = i + d[i] - 1;
l = i;
}
}
}
}
else
{
d[i] = 0;
while(d[i] + i < n && str[d[i]] == str[d[i]+i])
{
d[i]++;
if(i + d[i] - 1 > r)
{
r = i + d[i] - 1;
l = i;
}
}
}
}
}
int main()
{
scanf("%s", str);
int n = strlen(str);
init(n);
int ans = 0, maxx = 0;
int len = n / 3;
for(int i = len; i <= n - 2 * len; ++i)
maxx = max(maxx, d[i]);
while(len)
{
if(d[n-len] == len && maxx >= len)
{
ans = len; break;
}
len--;
if(len == 0) break;
maxx = max(maxx, d[len]);
maxx = max(maxx, max(d[n-2*len], d[n-2*len-1]));
}
printf("%d", ans);
return 0;
}