给出一个字符串S,找到一个最长的连续回文串。
例如串 babcbabcbaccba 最长回文是:abcbabcba
算法首先将输入字符串S, 转换成一个特殊字符串T,转换的原则就是将S的开头结尾以及每两个相邻的字符之间加入一个特殊的字符,例如#
例如: S = “abaaba”, T = “#a#b#a#a#b#a#”.
为了找到最长的回文字串,例如我们当前考虑以Ti为回文串中间的元素,如果要找到最长回文字串,我们要从当前的Ti扩展使得 Ti-d … Ti+d 组成最长回文字串. 这里 d 其实和 以Ti为中心的回文串长度是一样的. 进一步解释就是说,因为我们这里插入了 # 符号,对于一个长度为偶数的回文串,他应该是以#做为中心的,然后向两边扩,对于长度是奇数的回文串,它应该是以一个普通字符作为中心的。通过使用#,我们将无论是奇数还是偶数的回文串,都变成了一个以Ti为中心,d为半径两个方向扩展的问题。并且d就是回文串的长度。
例如 #a#b#a#, P = 0103010, 对于b而言P的值是3,是最左边的#,也是延伸的最左边。这个值和当前的回文串是一致的。
如果我们求出所有的P值,那么显然我们要的回文串,就是以最大P值为中心的回文串。
T = # a # b # a # a # b # a # P = 0 1 0 3 0 1 6 1 0 3 0 1 0
例如上面的例子,最长回文是 “abaaba”, P6 = 6.
根据观察发现,如果我们在一个位置例如 abaaba的中间位置,用一个竖线分开,两侧的P值是对称的。当然这个性质不是在任何时候都会成立,接下来就是分析如何利用这个性质,使得我们可以少算很多P的值。
下面的例子 S = “babcbabcbaccba” 存在更多的折叠回文字串。
当时当i = 15的时候,却只能得到回文 “a#b#c#b#a”, 长度是5, 而对称 i ' = 7 的长度是7.
扩充P[i] 之后,我们还要做一件事情是更新 R 和 C, 如果当前对称中心的最右延伸大于R,我们就更新C和R。在迭代的过程中,我们试探i的时候,如果P[i'] <= R - i, 那么只要做一件事情。 如果不成立我们对当前P[i] 做扩展,因为最大长度是n,扩展最多就做n次,所以最多做2*n。 所以最后算法复杂度是 O(n)
package use.java; /** * @ClassName: LongestPalindrome * @Description: 给出一个字符串S,找到一个最长的连续回文串。 * 例如串 babcbabcbaccba 最长回文是:abcbabcba * @date 2014年2月18日 下午3:18:48 */ public class LongestPalindrome { /** * @Title: preProcess * @Description: Transform S into T. * For example, S = "abba", T = "^#a#b#b#a#$". * ^ and $ signs are sentinels appended to each end to avoid bounds checking * @param s * @return */ public String preProcess(String s) { int n = s.length(); if (n == 0) return "^$"; String ret = "^"; for (int i = 0; i < n; i++) ret += "#" + s.substring(i, i + 1); ret += "#$"; return ret; } /** * @Title: longestPalindrome * @Description: TODO * @param s * @return */ public String longestPalindrome(String s) { String T = preProcess(s); int n = T.length(); int[] P = new int[n]; int C = 0, R = 0; char[] ch = T.toCharArray(); for (int i = 1; i < n - 1; i++) { int i_mirror = 2 * C - i; // equals to i' = C - (i-C) P[i] = (R > i) ? Math.min(R - i, P[i_mirror]) : 0; // Attempt to expand palindrome centered at i while (ch[i + 1 + P[i]] == ch[i - 1 - P[i]]) P[i]++; // If palindrome centered at i expand past R, // adjust center based on expanded palindrome. if (i + P[i] > R) { C = i; R = i + P[i]; } } // Find the maximum element in P. int maxLen = 0; int centerIndex = 0; for (int i = 1; i < n - 1; i++) { if (P[i] > maxLen) { maxLen = P[i]; centerIndex = i; } } return s.substring((centerIndex - 1 - maxLen) / 2, (centerIndex - 1 - maxLen) / 2 + maxLen); } public static void main(String[] args) { LongestPalindrome lp = new LongestPalindrome(); String s = "abbac"; String ret = lp.preProcess(s); System.out.println(ret); String res = lp.longestPalindrome(s); System.out.println(res); } }