最长子回文串 5. Longest Palindromic Substring Given a string s , find the longest palindromic substring in s . You may assume that the maximum length of s is 1000.
Example 1:
1 2 3 Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2:
1 2 Input: "cbbd" Output: "bb"
动态规划 如果一个字符串为回文串,那么除去他的首尾一个字符,得到的新字符依旧是回文串。即对于一个字符串S有
p(i,j) = p(i+1,j-1) and Si = Sj。P(i,j)表示从i到j的字符串。
从表达式可以看出,需要知道p(i,j),必须先知道p(i+1,j-1)。不如将外层循环倒置过来,先求i+1的值,再求j-1的值。
还有一点要注意的是初始值,关于j - i <3。如果长度为1的话,那么这个字符串必然为回文串,如果长度为2的话,又满足Si = Sj 那么他也必然是回文串。即他们不受上述表达式的约束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public String longestPalindrome (String s) { if (s == null || s.length() < 1 ){ return "" ; } int len = s.length(); boolean [][] dp = new boolean [len][len]; int subLength = 0 ; String res = null ; for (int i = len - 1 ; i>= 0 ;i--){ for (int j = i; j < len; j++){ dp[i][j] = s.charAt(i) == s.charAt(j) &&(j - i < 3 || dp[i+1 ][j-1 ]); if (dp[i][j] && (res == null || j - i + 1 > res.length())){ res = s.substring(i,j+1 ); } } } return res; }
中心扩展算法 首先先看一下回文串有什么特点,回文串的最大特点就是中心对齐。如“ababa”,他是以a为中心对齐。那么对于“abba”这种偶数的回文串,同样也是中心对齐的。他以两个b之间的字符之间为中心。因此我们可以以数组的每一个元素作为中心,找出最大的回文串。
我们可以设置回文串的头尾指针,start 和 end。如果有比目前的回文串更长的回文串,则修改start和end的的位置。这样子时间复杂度为O(n^2),空间复杂度为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public String longestPalindrome (String s) { if (s == null || s.length() < 1 ){ return "" ; } int sLen = s.length(); int start = 0 ,end = 0 ; for (int i = 0 ; i < sLen; i++){ int len1 = expandAroundCenter(s,i,i); int len2 = expandAroundCenter(s,i,i+1 ); int len = Math.max(len1,len2); if (len > end - start){ start = i - (len - 1 )/2 ; end = i + len/2 ; } } return s.substring(start,end+1 ); } private int expandAroundCenter (String s,int left,int right) { int L = left,R = right; while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)){ L--; R++; } return R - L -1 ; }
马拉车算法(Manacher’s Algorithm) 马拉车算法充分应用了回文串的对称性。和中心扩展法一样,我们以某个节点为中心,分别向两边扩展。为了保持字符的个数为奇数,在字符与字符之间插入特殊符号“#”来保证出力的字符个数是为奇数的。如“abab”变为“#a#b#a#b#”。假设新的字符串为T,我们使用P[i] 表示 T[i]处的回文半径。如以T[i]为中心的最长回文串为T[l,r],则P[i] = r - i + 1。这样求最长回文串的问题就转换为求数组P的问题了。
数组P怎么求,考虑。center 表示之前取得最大回文串的中心位置。right表示最大回文串能到达的最右端的值。
若i<right,则i之前的值必然计算出来了。我们可以利用回文串的性质。找出点i关于center的对称点j。j = 2* center - i。
如果P[j] < right -i。即以j为中心的回文串没有超出[left,right]的范围,由回文串的性质知道,两端的对应字符应该是相等的。所以有P[j] = P[i]。
如果P[j] >= right -i。即表明以j为中心的最大回文串超出了范围。在范围内的部分肯定是复合回文串的性质,但是对于超出的部分,只要从right+1开始一一匹配,从而更新right和对应的center,
若i > right 无法利用回文串的性质,只能一步一步去匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public String longestPalindrome (String s) { if (s == null || s.length() < 1 ){ return s; } int sLen = s.length(); StringBuilder t = new StringBuilder(); t.append("#" ); for (int i = 0 ; i < sLen; i++){ t.append(s.charAt(i)); t.append("#" ); } int tLen = t.length(); int center = 0 ; int right = 0 ; int maxCenter =0 ; int maxLen = 1 ; int [] p = new int [2 *sLen + 1 ]; for (int i = 0 ; i < tLen; ++i){ p[i] = i<right?Math.min(p[2 *center -i],right -i):1 ; while (i - p[i] >= 0 && i + p[i]<tLen && t.charAt(i - p[i]) == t.charAt(i + p[i])){ p[i]++; } if (right < i + p[i]){ right = i + p[i]; center = i; } if (maxLen < p[i]){ maxLen = p[i]; maxCenter = i; } } return s.substring((maxCenter - maxLen + 1 )/2 ,(maxCenter - maxLen + 1 )/2 + maxLen - 1 ); }