马拉车算法,回文子串
马拉车算法
- 原始中心扩散法
每个字符、或者两个字符空隙为中心向两边扩散,判断最长回文子串长度。
时间复杂度O(n*n)
- 马拉车算法
核心点: 使用已经计算过的回文长度根据回文中心对称特点快速得到当前字符的最长回文长度。
预处理原始字符串
如果原始字符串origin为: abac
预处理str后: #a#b#a#c#
可以得知:不论原始字符串长度是偶数还是奇数,预处理后都是奇数。
算法流程
-
使用预处理字符串str
-
创建P数组:我们通过一个数组P保持当前字符向两边扩展的最大长度,P数组长度为str.length()。
-
使用字符i之前已匹配的最右端的right_index以及right_index对应的回文中心字符center_index,来辅助更新当前P[i]
-
初始化max_center_index=0,max_right_index=0, max_len=1 , max_index=0;预处理字符串为pre
---------------------
-
right_index定义:表示当前字符由中心向两边扩散到达的最右边的索引值。初始值:0
-
Max_right_index定义:当遍历当前字符时,已存在的最大right_index。初始值:0
-
max_center_index定义:Max_right_index对应的回文中心索引,初始值:0。距离Max_right_index=6,max_center_index=3.
-
max_len:最大的回文长度,默认值为1
-
max_index:最大的回文长度对应的中心索引,初始值:0。
-
i_mirror: 当前遍历索引对于max_center对称的索引。
origin | a | b | a | c | |||||
---|---|---|---|---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | |||||
str | # | a | # | b | # | a | # | c | # |
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
参数 | left_index | i_mirror | max_center_index | i | max_right_index | ||||
P | 0 | 1 | 0 | 3 | |||||
P[i_mirror] | P[i] |
str_index=origin_index*2+1
origin_index=str_index/2
P[i_mirror]表示:以i_mirror为中心的回文,向两边扩散的长度。
因此
if i<max_right_index
i_mirror=max_center-(i-max_center)=2*max_center-i
//P[i_mirror]只考虑向左的长度,因为向右扩散对于P[i]向左扩散来讲是一样的。因此max_right_index-i与P[i_mirror]必须取最小的。
P[i]=Math.min(max_right_index-i,P[i_mirror])
else
P[i]=0
- 首先计算以max_center_index为中心的i_mirror节点,
i_mirror=2*max_center_index-i
,根据回文中心对称可知,i_mirror节点的最长回文长度与i节点存在关联, - 如果i<max_right_index;
- 计算P[i],如果 i+P[i_mirror]大于等于max_right_index,则可以证明(max_right_index-i)区间是以i为中心的回文最小长度 ,如果 i+P[i_mirror]小于max_right_index,则可以证明 (P[i]=P[i_mirror] 。 所以设置
P[i]=Math.min(P[i_mirror],max_right_index-i)
。 - 如果i>=max_right_index,则P[i]=0,然后开始中心扩展法
- 开始中心扩散,并更新P[i],扩散完成后判断是否需要更新max_center_index、max_right_index、max_len、max_index
- 循环完成后,计算原始字符串起始索引,因此:原始索引开始start=(max_index-max_len)/2,最终结果为:origin.substring(start,start+max_len);
算法实现
//预处理字符串
public String preProcess(String str,char c){
if(str.indexOf(c)>=0){
throw new RuntimeException("不能使用字符串已存在的字符");
}
StringBuilder sb=new StringBuilder();
for(char cur:str.toCharArray()){
sb.append(c);
sb.append(cur);
}
sb.append(c);
return sb.toString();
}
//马拉车算法
public String manacher(String origin){
//判断边界情况
if(null==origin || origin.trim().equals("")){
return "";
}
if(origin.length()==1){
return origin;
}
//预处理
String str=preProcess(origin,'#');
//初始化p数组
int[] P=new int[str.length()];
//初始化变量
int max_center_index=0,max_right_index=0,max_len=1,max_index=0;
for(int i=0;i<str.length();i++){
int i_mirror=2*max_center_index-i;
if(i<max_right_index){
P[i]=Math.min(max_right_index-i,P[i_mirror]);
}
//中心扩展法
while( (i-P[i]-1)>=0 && (i+P[i]+1)<str.length()){
if(str.charAt(i-P[i]-1)==str.charAt(i+P[i]+1)){
P[i]++;
}else{
break;
}
}
//更新结果
if( (i+P[i])>max_right_index ){
max_right_index=i+P[i];
max_center_index=i;
}
if(P[i]>max_len){
max_len=P[i];
max_index=i;
}
}
//计算开始索引 原始字符串与预处理字符串存在 /2取整
int start=(max_index-max_len)/2;
return origin.substring(start,start+max_len);
}