问题:给定一个字符串,求它的最长回文子串长度。
提示:如果一个字符串正着读和反着读是一样的,那它就是回文串。下面是一些回文串的实例:ada,acca...
输入:addaf
输出:4
解题思路:
1.所有的回文串都是对称的。长度为奇数回文串以最中间字符的位置为对称轴左右对称,而长度为偶数的回文串的对称轴在中间两个字符之间的空隙。所以我可以遍历这个字符串的每个字符,每次计算都假设每个字符是回文的中心(所以其实是在枚举回文中心)然后将这个字符向左向右扩展直至不重复为止,遍历一次下来就能找到最大的回文子串。时间复杂度O(n^2)
代码(C 环境:Xcode)
int func(char *s){
int n,i,j,c,max;
n = strlen(s);
if(n < 1){
return 0;
}
else{
max = 0;
for (i = 0; i < n; i++) { // i is the middle point of the palindrome
for (j = 0; (i - j >= 0) && (i + j < n); j++){ // for the odd case
if (s[i - j] != s[i + j]){
break;}
c = j * 2 + 1;
}
if (c > max)
max = c;
for (j = 0; (i - j >= 0) && (i + j + 1 < n); j++){ // for the even case
if (s[i - j] != s[i + j + 1]){
break;}
c = j * 2 + 2;
}
if (c > max)
max = c;
}
}
return max;
}
存在的问题
1.代码复杂
回文长度的奇偶性影响很大(回文中心是字符之间的空隙还是字符?)代码是分开的。
2.重复访问
举个例子
char: a b a b a
i : 0 1 2 3 4
当i==1,和i==2时,左边的子串aba分别被遍历了一次。
解决办法:Manacher 算法
(1)Manacher算法首先对字符串做一个预处理,在所有的空隙位置(包括首尾)插入同样的符号,要求这个符号是不会在原串中出现的。这样会使得所有的串都是奇数长度的。以插入*号为例:
aba:*a*b*a* //回文长度L=奇数
abba:*a*b*b*a* //回文长度L=奇数
插入的是同样的符号,且符号不存在于原串,因此子串的回文性不受影响,原来是回文的串,插完之后还是回文的,原来不是回文的,依然不会是回文。
(2)我们把一个回文串中最左或最右位置的字符与其对称轴的距离称为回文半径。Manacher定义了一个回文半径数组RL,用RL[i]表示以第i个字符为对称轴的回文串的回文半径。我们一般对字符串从左往右处理,因此这里定义RL[i]为第i个字符为对称轴的回文串的最右一个字符与字符i的距离。对于上面插入分隔符之后的两个串,可以得到RL数组:
char: # a # b # a #
RL : 1 2 1 4 1 2 1
RL-1: 0 1 0 3 0 1 0
i : 0 1 2 3 4 5 6
char: # a # b # b # a #
RL : 1 2 1 2 5 2 1 2 1
RL-1: 0 1 0 1 4 1 0 1 0
i : 0 1 2 3 4 5 6 7 8
上面我们还求了一下RL[i]-1。通过观察可以发现,RL[i]-1的值,正是在原本那个没有插入过分隔符的串中,以位置i为对称轴的最长回文串的长度。那么只要我们求出了RL数组,就能得到最长回文子串的长度。
于是问题变成了,怎样高效地求的RL数组。基本思路是利用回文串的对称性,扩展回文串。
具体可以参考(关于Manacher我见过最详细的帖):
<a href="https://segmentfault.com/a/1190000003914228">Manacher传送门</a>
代码(C 环境:Xcode)
void make_odd(char *s){//pre-process the string, make length be the odd
int len_1,len_2,i;
len_1 = strlen(s);
len_2 = 2*len_1 + 1;
for(i = len_1-1;i>=0;i--){
s[2*i +1] = s[i];
s[2*i]='*';
}
s[len_2-1]='*';//last_position
s[len_2]='\0';
}
int func_2(char *s){
int i,MaxRight,pos,MaxLen,RL[100];
for(i=0;i<strlen(s);i++){
RL[i] = 0;
}//RL=[0]*len(s)
MaxRight=0;
pos=0;
MaxLen=0;
//for i in range(len(s)):
for(i=0;i<strlen(s);i++){
if(i<MaxRight){
RL[i] = RL[2*pos-i]<MaxRight-i?RL[2*pos-i]:MaxRight-i;
//RL[i]= min (RL[2*pos-i], MaxRight-i)
}
else{
RL[i]=1;
}
while( i-RL[i]>=0 && i+RL[i]<strlen(s) && s[i-RL[i]]==s[i+RL[i]]){
RL[i]+=1;
}
if(RL[i]+i-1>MaxRight){
MaxRight=RL[i]+i-1;
pos=i;
}
MaxLen=MaxLen>RL[i]?MaxLen:RL[i];//update MaxLen
}
return MaxLen-1;
}
复杂度分析
空间复杂度:插入分隔符形成新串,占用了线性的空间大小;RL数组也占用线性大小的空间,因此空间复杂度是线性的。
时间复杂度:尽管代码里面有两层循环,但Manacher的时间复杂度是线性的。由于内层的循环只对尚未匹配的部分进行,因此对于每一个字符而言,只会进行一次,因此时间复杂度是O(n)。
References
1.<a href =https://segmentfault.com/a/1190000003914228)>最长回文子串——Manacher 算法的python实现 </a>
2.LeetCode:Longest Palindromic Substring 最长回文子串