问题描述:编写一个函数,参数为一个字符串,返回这个字符串的最长回文子串长度。
解决方法:
方法一:因为如果一个字符串是回文的,那么以某个字符为基点的前缀和后缀都是相同的。所以可通过遍历某个字符作为基点,然后再基点处前后进行扫描,记录并更新得到最长的回文子串。
实现代码如下:
public static int getLongestPalindrome(String str) {
int len = str.length();
if (str == null || len <= 0)
return 0;
int max = 0, c = 0, i = 0, j;
// i为基点位置,j为前后缀长度
for (; i < len; i++) {
//回文子串长度为奇数的情况
for (j = 0; i - j >= 0 && i + j < len; j++) {
if (str.charAt(i - j) != str.charAt(i + j))
break;
c = j * 2 + 1;
}
if (c > max)
max = c;
//回文子串长度为偶数的情况
for (j = 0; i - j >= 0 && i + j + 1 < len; j++) {
if (str.charAt(i - j) != str.charAt(i + j + 1))
break;
c = j * 2 + 2;
}
if (c > max)
max = c;
}
return max;
}
方法二:基于Manacher算法。Manacher算法描述如下:
首先,在每个字符的两边都插入一个特殊的符号,将所有可能的基数或偶数长度的回文子串都转换成了奇数长度。比如"abba"转换成"#a#b#b#a#","aba"转换成"#a#b#a#"。
此外为了进一步减少编码的复杂度,可以在字符串的起始处添加另一个特殊字符,这样就不需要处理越界问题。比如"$#a#b#a#"。
以字符串S = "abbabcba"为例,插入'$'和'#'这两个特殊符号,转换成"$#a#b#b#a#b#c#b#a#",然后用一个数组P[i]来记录以字符S[i]为基点的最长回文子串前缀或后缀的长度(包括S[i])。
此时字符串S与数组P的对于关系如下:
符可以看出,P[i]-1刚好是源字符串中最长回文串的总长度,为5。
接下来怎么计算P[i]呢?Manacher算法增加两个辅助变量id和mx,其中id表示最大回文子串基点位置,mx则为id+P[id],也就是最大回文子串的边界。由此可得到一个很重要的结论:如果 mx > i,则 P[i] >= Min(P[2 * id - 1],mx - i)。
实现代码如下:
//mx > i,那么P[i] >= MIN(P[2 * id - i], mx - i)
//故谁小取谁
if (mx - i > P[2*id - i])
P[i] = P[2*id - i];
else //mx-i <= P[2*id - i]
P[i] = mx - i;
下面,令j = 2*id - i,也就是说j是i关于id的对称点。
当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于i和j对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有P[i] = P[j];
当 P[j] >= mx - i 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,再具体匹配。
此外,对于 mx <= i 的情况,因为无法对 P[i]做更多的假设,只能让P[i] = 1,然后再去匹配。
综上,关键代码如下:
//输入,并处理得到字符串s
int p[1000], mx = 0, id = 0;
memset(p, 0, sizeof(p));
for (i = 1; s[i] != '\0'; i++)
{
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (s[i + p[i]] == s[i - p[i]])
p[i]++;
if (i + p[i] > mx)
{
mx = i + p[i];
id = i;
}
}
//找出p[i]中最大的
此Manacher算法使用id、mx做配合,可以在每次循环中,直接对P[i]的快速赋值,从而在计算以i为中心的回文子串的过程中,不必每次都从1开始比较,减少了比较次数,最终使得求解最长回文子串的长度达到线性O(N)的时间复杂度。
参考:http://www.felix021.com/blog/read.php?2040