1.Manacher算法(计算回文串)
(1)小改造
中心拓展法求回文串都会吧,是不是分类讨论了,回文串长度为奇数和回文串长度为偶数两种情况。
马拉车呢很聪明,他将代求串进行下边这种改造:
这样做有什么好处呢?
不管原回文串长度是奇数还是偶数,在T中长度都是奇数
(2)辅助数组Len[]
len[i] 表示以i为中心的回文串的半径
len[i]-1就是S中回文串的长度(这个自己想一下就行,很简单的,如果有哨兵,这个不满足)
(3)Len数组的计算
首先从左往右依次计算Len[i],当计算Len[i]时,Lenj已经计算完毕。设P为之前计算中各回文串右端点能到达的最大值,并且设取得这个最大值的回文串的中心为po,那么就有两种情况:
3.1:i<=P
3.1.1 Len[j]<P-i (就是如图所示,Po的回文串能包含j的回文串)
很显然Len[i]=Len[j]了
3.1.2 Len[j]>=P-i (如下图所示,下图画的也不是很准,反正就是Po不能包含或者恰好包含j时)
我们只能确定Len[i]>=P-i,后边也可能还会回文,所以就只能老老实实匹配了
3.2:i>P
这种情况就不能在之前的Len中获得有用的长度信息了,所以从长度为1开始匹配就完事了。
没了,简单吧
(4)时间复杂度分析
emmm,都说是O(n),和Z算法类似,但是吧我不会分析,等以后咱也弄懂了,就回来补上
(5)模板代码
有一点要指出的是:如果用了哨兵len[i]-1可能不再是原串中回文串的长度,因为第0个字符是哨兵,是不可能与添加的’$'匹配的,这就导致len[i]可能就是原回文串,自己想(本来改造添加的字符能互相匹配所以len[i]-1才是原回文串的长度,你这改造添加的第一个字符不能匹配了,所以就不用-1了)
//参数是字符串,结果是改造后的数组对应的半径
public int[] manacher(String x){
if(x==null||x.length()==0) return null;
char[] chars=new char[x.length()*2+1];
for(int i=0;i<x.length();i++){
chars[2*i]='$';
chars[2*i+1]=x.charAt(i);
}
chars[0]='&';
chars[chars.length-1]='*';
int p=-1;
int pi=-1;
int []len=new int[chars.length];
len[0]=1;
len[len.length-1]=1;
for(int i=1;i<len.length-1;i++){
if(p>i){//可以尝试不用匹配
int mirror=2*pi-i;
int mlen=len[mirror];
if(i+mlen-1<p){//完全不用自己撇皮
len[i]=mlen;
}else{//还要自己匹配一段
int mylen=p-i+1;
int left=i-mylen;
int right=i+mylen;
while(chars[left--]==chars[right++]) mylen++;//哨兵:不与任何人匹配(不和任何人配对,不会被情所困,这就是哨兵的魅力)所以不需要再加越界判断,但是有利有弊,为len[i]-1=原字符串中回文串长度 这个等式画上了不等号
len[i]=mylen;
}
if(i+len[i]-1>p){
p=i+len[i]-1;
pi=i;
}
}else{//必须要自己匹配了
int mylen=1;
int left=i-1;
int right =i+1;
while(chars[left--]==chars[right++]) mylen++;
len[i]=mylen;
if(i+len[i]-1>p){
p=i+len[i]-1;
pi=i;
}
}
}
return len;
}