最长回文子串
点击这里查看原题
题目描述
给定一个字符串s,输出它最长的回文子串
Manacher(马拉车)算法
其实本篇文章注重想讲讲这个Manacher算法。其实这个题目我们容易想到,遍历字符串s的每一个字符,假设遍历到i,那么就把这个字符当做回文中心,即回文串的对称轴,然后从两边开始扩张,直到不是回文的为止,记录下来最大长度的起点与终点即可,这种算法叫中心扩展法。中心扩展法还得分回文中心长度为1和2两种情况考虑,因此时间复杂度为O(n²)。
而Manacher算法运用了动态规划+中心扩展法优化了上述算法,充分利用了回文串的对称性。
首先,我们得把字符串的每一个空隙都加上一个不可能存在的字符,比如说’#’,如图
为什么要这样做呢?原因是要把字符串长度变为奇数,这样就可以不用讨论回文中心的长度是1是2的情况了。证明:假设len为原字符串长度,一共有len-1个空隙,加上头尾两个符号,因此新字符串长度为 len + len -1 + 2 = 2*len + 1是一个奇数。
同时,我们可以发现,在新字符串中,以某一个字符为回文中心的
(最大回文子串长度 - 1)/2 = 原来字符串中以该字符为回文中心的回文子串长度 = 从该字符出发走到最右端所需步数(即回文半径)
因此我们把所有以i为回文中心的回文半径求出来即可。
为了下面能解释的更清楚,先说明几个定义:
1. 回文半径r:从回文中心出发,走到该回文串最右端所需距离。
2. f(i):i为回文中心,f(i)则是以i为回文中心的最大回文半径。
3. maxRight:某个回文中心的最大回文子串的最右端的下标。
本算法最根本的思路就是通过动态规划和中心扩展法求出每一个f(i),
我们可以考虑以下两种情况:
当 i >= maxRight
(注意,图中的s是加了符号填充之后的字符串)
当 i > maxRight 时,我们只能根据中心扩展法法来求出以i为回文中心的最大回文子串长度。
当 i < maxRight
这种条件下我们就要充分利用回文串的对称性来求出f(i)
我们可以分三种情况讨论:
注意:以下的mirror都是i关于center的对称点
①f(mirror) < maxRight - i
这种情况可以这样描述:f(mirror)要小于mirror到左端点的距离,而根据对称性,mirror到左端点的距离等于i到maxRight的距离,因此有: f(mirror) < maxRight - i
那么f(i)该怎么求呢?我们很容易想到,在i到maxRight之间的每一个字符与i到center之间的每一个字符都分别与mirror到左端点之间的每一个字符和mirror到center之间的每一个字符对称。 因此f(i) = f(mirror)
②f(mirror) = maxRight - i
当这种情况时,我们最多只能确认f(i) >= maxRight - i的,因为根据对称性,很容易就得出来,以i为中心,半径为maxRighht - i 的子串必定回文,但是我们无法确认,扩充之后是否还是回文串,我们只能这样描述:当f(mirror) = maxRight - i ,f[i]至少等于maxRight - i,然后再以maxRight为中心进行中心扩展
③f(mirror) > maxRight - i
在②中,我们很容易得出,f(i) = maxRight - i ,那么为什么f(i) 有可能等于 f(mirror)吗,答案是不可能,假设f(i) = f(mirror),也就是说,以center为中心的最大子串的最右端应该大于maxRight,因此矛盾。所以,f(i) = maxRight - i
综上所述:
当i>=maxRight ,只能通过中心扩展法得出f(i)
当i<maxRight,f(i)>= min{ f(mirror),maxRight - i },再尝试中心扩展法
代码
class Solution {
public String longestPalindrome(String s) {
StringBuilder sb = new StringBuilder();
int n = s.length();
for(int i=0;i<n;i++){
sb.append('#');
sb.append(s.charAt(i));
}
sb.append('#');
String newStr = sb.toString();
int newLength = newStr.length();
int[] r = new int[newLength];
int start = 0;
int center = 0,maxRight = 0;
int maxLen = 1;
for(int i=0;i<newLength;i++){
if(i<maxRight){
int mirror = 2*center - i;
f[i] = Math.min(f[mirror],maxRight - i);
}
//尝试进行中心扩展法
int left = i - f[i] - 1;
int right = i + f[i] + 1;
while(left>=0 && right<newLength && newStr.charAt(left) == newStr.charAt(right)){
--left;
++right;
f[i]++;
}
//如果扩展完后发现此时最右端的下标比maxRight大,就要重置
if(i + f[i] > maxRight) {
maxRight = i + f[i];
center = i;
}
//记录最大长度
if(r[i] > maxLen){
maxLen = f[i];
//这个起点的计算方法读者自行证明吧
start = (i - maxLen)/2;
}
}
return s.substring(start,start + maxLen);
}
}