题目:
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
链接:https://leetcode.com/problems/longest-palindromic-substring/
题解:
卡在这道题很久,直接导致没有继续刷题的动力。这道题有许多种解法,下面分别来看一看最简单的中心展开法, Manacher算法,后缀树suffix tree,以及使用Rabin-carp rolling hash方法
1) 中心展开法:
从头遍历数组,考虑Palindrome是奇数和偶数两种情况。 Time Complexity - O(n2), Space Complexity - O(1)
public class Solution { public String longestPalindrome(String s) { // O(n * n) - go from middle if(s == null || s.length() == 0) return ""; String res = s.substring(0, 1); for(int i = 0; i < s.length(); i++) { String tmp = getSubstring(s, i, i); if(tmp.length() > res.length()) res = tmp; tmp = getSubstring(s, i, i + 1); if(tmp.length() > res.length()) res = tmp; } return res; } private String getSubstring(String s, int lo, int hi) { while(lo >= 0 && hi <= s.length() - 1 && s.charAt(lo) == s.charAt(hi)) { lo--; hi++; } return s.substring(lo + 1, hi); } }
2) Manacher's Algorithm
非常天才的方法,充分利用了Palindrome的特性,代码绝大部分使用了Sedgewick教授的。 Time Complexity - O(n), Space Complexity - O(n)
- 先对原字符串等距离插入'#',可以略去奇偶Panlindrome的判定
- 以当前center中心设置 i 的mirror,假如 i 仍然在当前center最长Palindrome的右边界范围内,依据Palindrome的特性, p[i] = Math.min(right - i, p[mirror])
- 以i为中心进行扩展,计算出p[i] - 当前的最长Palindrome
- 假如以i为中心的最长Palindrome超过了右边界,更新i和right
public class Solution { //mostly coded by Sedgewick private int[] p; private String s; private char[] t; //transformed string public String longestPalindrome(String s) { this.s = s; preprocess(); p = new int[t.length]; int center = 0, right = 0; for(int i = 1; i < t.length - 1; i++) { int mirror = 2 * center - i; if(right > i) p[i] = Math.min(right - i, p[mirror]); while(i - p[i] >= 0 && i + p[i] < p.length && t[i + p[i]] == t[i - p[i]]) //try to expand p[i]++; if(i + p[i] > right){ center = i; right = i + p[i]; } } int maxLength = 0; center = 0; for (int i = 1; i < p.length-1; i++) { // i is center, p[i] is half length of longest Palindrome in t if (p[i] > maxLength) { maxLength = p[i]; center = i; } } return s.substring((center - p[center] + 2) / 2, (center + p[center]) / 2); } private void preprocess() { //preprocess to avoid old/even length t = new char[s.length() * 2 + 1]; for(int i = 0; i < s.length(); i++) { t[2 * i] = '#'; t[2 * i + 1] = s.charAt(i); } t[t.length - 1] = '#'; } }
3) Suffix Tree
后缀树/后缀词典/后缀数组应该是这类问题的终极解决方法了。对于这个问题后缀树不如Manacher有效率。不过我们还是来看一看怎么解决这个问题。 (待补充)
4) Rabin-carp, rolling hash
二刷:
Java: Manacher: Time Complexity - O(n), Space Complexity - O(n) - 1/3/2016
二刷的时候仍然被这算法卡了几天。今天再次尝试叙述一下逻辑:
- preprocess,将输入字符串s等间距插入特殊字符'#',目的是为了以后计算方便,不用太多考虑长度的奇偶性,不preprocess其实也可以
- char[] t代表经过preprocess以后的transformed string
- int[] p, p[i]是length of longest Palindromic string centerred at i,就是以i为中心,最长Palindromic string的长度
- mirror代表以当前的center为中心,坐标i在center左边的镜像。因为 i - center = center - mirror,所以mirror = 2 * center - i。
- 下面比较重要的一点就是Manacher's Algorithm最主要的性质, 这里利用了之前计算过的最长的Palindromic String。假设之前计算过一个很长很长的Palindromic String,其中心在center, 那么我们遍历当前center的后面的元素 i 的时候,假如这个i在之前那个很长的Palindromic String的右边界范围内,那么我们就可以得到一个公式来节约重新匹配 - p[i] = Math.min(p[mirror], right - i), 即 p[i] 的当前最小值等于 p[mirror] 和 right - i这两个值中的较小者。 这里p[mirror]是以这个mirror为中心的最长回文字符串的长度。
- 就比如"#a#b#a#a#b#a#",假如当前的center在两个"a"中间的"#",假设我们正计算以之后那个"a"为中心的结果, 因为之前的p[mirror]等于2,所以这里p[i]至少等于2和right - i中的较小者。
- 假如我们正计算第二个"b"为中心的结果,因为之前的p[mirror]等于4,所以p[i]至少等于4和right - i中的较小者。
- 有了p[mirror]和right - i这两个边界,我们就可以节约许多的重复matching
- 上一步确定了p[i]的最小值以后,我们就可以继续进行尝试扩展当前Palindromic。这个时候我们没有取巧的办法,只能在t中对t[i + p[i]]和t[i - p[i]]进行逐字符对比,假如他们相同,则我们增加p[i]的长度,直到求出以i为中心的最长回文串长度
- 在上一步找出了以i为中心的最长回文串长度后,我们比较一下当前字符串的右边界是否大于历史最长回文串右边界"right",假如当前回文串更长,则我们更新center = i, 新的右边界right = p[i] + i。
- 这里我们就完成了p[i]的计算,也就是我们得到了每个以i为中心的最长回文字符串的长度。 这个时候我们再遍历一遍数组,找到其中最长的那一个,然后再对原始字符串进行substring操作就可以了
public class Solution { public String longestPalindrome(String s) { if (s == null || s.length() == 0) { return s; } char[] t = new char[s.length() * 2 + 1]; // transformed string int[] p = new int[t.length]; // p[i] is length of longest Palindromic string centered at i preprocess(s, t); int center = 0, right = 0; for (int i = 1; i < t.length - 1; i++) { int mirror = 2 * center - i; // center - mirror = i - center, mirror is i's mirror based on center if (i < right) { // if i within pre-calculated palindrome boundary p[i] = Math.min(p[mirror], right - i); //then p[i] is at least p[mirror] } // or right - i while (i + p[i] < t.length && i - p[i] >= 0 && t[i + p[i]] == t[i - p[i]]) { // try to expand current Palindrome p[i]++; } if (i + p[i] > right) {// if new palindromic string right boundary > old boundary, update center and right boundary center = i; right = i + p[i]; } } center = 0; int maxLen = 0; for (int i = 1; i < p.length - 1; i++) { if (p[i] > maxLen) { center = i; maxLen = p[i]; } } return s.substring((center - p[center] + 2) / 2, (center + p[center]) / 2); } private void preprocess(String s, char[] t) { for (int i = 0; i < s.length(); i++) { t[2 * i] = '#'; t[2 * i + 1] = s.charAt(i); } t[t.length - 1] = '#'; } }
Python:
class Solution(object): def longestPalindrome(self, s): """ :type s: str :rtype: str """ t = '#' for char in s: # preprocess t += char + '#' p = [0] * len(t) # p[i] is longest Palindromic string centered at i center = right = 0 for i in range(1, len(t) - 1): mirror = 2 * center - i # mirror is mirror of i centered at center, center - mirror = i - center if i < right: # if i within range of lps, i is at least min(p[mirror], right - i) p[i] = min(p[mirror], right - i) while i + p[i] < len(t) and i - p[i] >= 0 and t[i - p[i]] == t[i + p[i]]: # try expand current palindromic string p[i] += 1 if i + p[i] > right: # try update right center = i right = i + p[i] center = 0 maxLen = 0 for i in range(1, len(t) - 1): # find the longest one if p[i] > maxLen: center = i maxLen = p[i] return s[(center - p[center] + 2) / 2 : (center + p[center]) / 2]
Reference:
http://articles.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html
http://algs4.cs.princeton.edu/home/
http://en.wikipedia.org/wiki/Longest_palindromic_substring
http://www.felix021.com/blog/read.php?2040 <- 中文
http://stackoverflow.com/questions/10468208/manachers-algorithm-algorithm-to-find-longest-palindrome-substring-in-linear-t