最长回文子串

来源:互联网 发布:js cookie 过期时间 编辑:程序博客网 时间:2024/06/04 20:10

问题描述:编写一个函数,参数为一个字符串,返回这个字符串的最长回文子串长度。


解决方法:


方法一:因为如果一个字符串是回文的,那么以某个字符为基点的前缀和后缀都是相同的。所以可通过遍历某个字符作为基点,然后再基点处前后进行扫描,记录并更新得到最长的回文子串。

实现代码如下:

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,然后再去匹配。

综上,关键代码如下:

//输入,并处理得到字符串sint 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 


0 0
原创粉丝点击