首先还是来看看问题:
给出一个长为N的字符串S,再给出一个长为M的字符串T
求S的所有后缀中和T的最长公共前缀
显然可以想到暴力的做法,枚举S所有的后缀,然后和T做匹配,时间复杂度为 O(NM)
显然,这个方法和之前的暴力一样,都处理了太多的重复操作,那么可以用类似KMP的方法来处理吗?
答案是肯定的,也就是Extend-KMP算法
可以先用类似KMP的想法,用 next 数组保存 T[i..M−1] 与 T[0..M−1] 的最长公共前缀,但是有一个特例: next[0]=len
接着设p为答案伸得最远的后缀的下标,也就是 T[p..M−1] ,但是这个后缀不包括原串,
如果这两个串的公共前缀没有包括 T[i] ,那么就要去暴力求解,如果包括就可以尝试像 manacher 一样用前面已经求出的答案。
接下来举个栗子:
字符串 T=aaaabaaaa
那么 next=9,3,2,1,0,4,???
根据之前求出的,可以知道 T[0..3]=T[5..8] ,并且 T[1..3]=T[0..2] ,然后把 T[0..3] 和 T[5..8] 各去掉相同位置的一位,仍然相等,也就是 T[1..3]=T[6..8]
根据之前的结果推导出 T[6..8]=T[1..3]=T[0..2] 因此, next[6] 应该至少为3
然后检查是否能够增加,发现 S[9]≠S[3] 所以 next[6] 不能增加,也就是3
用类似这样的方法,同样可以直接推导出extend的结果,那么就得到了答案。
以下为代码:
void get_next() {
int len = strlen(T);
_next[0] = len;
int a = 0;
while(a < len - 1 && T[a] == T[a + 1]) {
++a;
}
_next[1] = a;
a = 1;
for(int k = 2; k < len; ++k) {
int p = a + _next[a] - 1;
int L = _next[k - a];
if(k - 1 + L >= p) {
int j = (p - k + 1) > 0 ? (p - k + 1) : 0;
while(k + j < len && T[k + j] == T[j]) {
++j;
}
_next[k] = j;
a = k;
} else {
_next[k] = L;
}
}
}
void ExtendKMP() {
int i = 0, j, po, lenS = strlen(S), lenT = strlen(T);
get_next();
while(S[i] == T[i] && i < lenT && i < lenS) {
i++;
}
ex[0] = i;
po = 0;
for(i = 1; i < lenS; i++) {
if(_next[i - po] + i < ex[po] + po) {
ex[i] = _next[i - po];
} else {
j = ex[po] + po - i;
if(j < 0) {
j = 0;
}
while(i + j < lenS && j < lenT && S[j + i] == T[j]) {
j++;
}
ex[i] = j;
po = i;
}
}
}
代码有点长……
最后来看看时间复杂度:
先看求 next
因为内部while每运行一次,j至少加1,最多执行M次,再加上外层循环的语句,时间复杂度为 O(M)
求 extend 同理,所以总时间为 O(M+N)
但是相对于KMP来说,这个算法的常数比较差,所以实际运行时应该会慢一点,但对于不是线性时间求出答案的方法,是很快的。