總結最長迴文子串的幾種做法 Longest Palindrome Substring
阿新 • • 發佈:2019-01-10
題目是:找出一個字串中的最長迴文子串。
例如:abcbcbb 的最長迴文子串是 bcbcb
首先一種常見的錯誤方法是把原字串S倒轉過來成為S‘,以為這樣就將問題轉化成為了求S和S’的最長公共子串的問題。反例S="abacdfgdcaba",若按這種解法得到答案是:"abacd",顯然不是迴文,而正確答案是"aba"
下面總結一下四種解法:(面試時推薦中心展開法)
1)暴力法:Time:O(n^3), Space:O(1)
2)DP法:Time:O(n^2), Space:O(n^2)
3)中心展開法:Time:O(n^2), Space:O(n) ***推薦面試時用!!!
4)Manacher演算法:Time:O(n),Space: O(n) (精妙但較麻煩)
下面是實現程式碼:
package String; public class LongestPalindromeSubstring { // 暴力法 Time Complexity: O(n^3) public static String longestPalindromeBruteForce(String s) { String longest = ""; if(s.isEmpty()) { return longest; } for(int i=0; i<s.length(); i++) { // 開始位置 for(int j=i; j<s.length(); j++) { // 結束位置 String substr = s.substring(i, j+1); if(j-i+1 > longest.length() && isPalindrome(substr)) { longest = substr; } } } return longest; } // O(n) 檢查是否為迴文 private static boolean isPalindrome(String s) { int len = s.length(); for(int i=0; i<=len/2; i++) { if(s.charAt(i) != s.charAt(len-1-i)) { return false; } } return true; } // =========================================== // 動態規劃1 時間複雜度O(N2), 空間複雜度O(N2) public static String longestPalindromeDP1(String s) { int len = s.length(); int longestBegin = 0; int maxLen = 1; boolean[][] isPalindrome = new boolean[len+1][len+1]; for(int i=0; i<len; i++) { isPalindrome[i][i] = true; } for(int i=0; i<len-1; i++) { if(s.charAt(i) == s.charAt(i+1)) { isPalindrome[i][i+1] = true; longestBegin = i; maxLen = 2; } } for(int l=2; l<=len; l++) { // 迴文子串的長度 for(int i=0; i<len-l+1; i++) { // 迴文子串的開始位置 int j = i+l-1; // 迴文子串的結束位置 if(isPalindrome[i+1][j-1] && s.charAt(i) == s.charAt(j)) { isPalindrome[i][j] = true; longestBegin = i; maxLen = l; } } } return s.substring(longestBegin, longestBegin+maxLen); } // =========================================== // 中心展開法 時間複雜度O(N2), 空間複雜度O(1) public static String longestPalindromeExpand(String s) { int len = s.length(); if(len == 0) { return ""; } String longest = s.substring(0, 1); for(int i=0; i<len; i++) { // 當迴文為奇數長度時 String p1 = expandAroundCenter(s, i, i); if(p1.length() > longest.length()) { longest = p1; } // 當迴文為偶數長度時 String p2 = expandAroundCenter(s, i, i+1); if(p2.length() > longest.length()) { longest = p2; } } return longest; } // c1, c2為展開的中心位置 private static String expandAroundCenter(String s, int c1, int c2) { int l = c1, r = c2; int len = s.length(); // 如果檢查位置相等,則分別往左右展開 while(l>=0 && r<=len-1 && s.charAt(l)==s.charAt(r)) { l--; r++; } return s.substring(l+1, r); // 迴文子串 } // =========================================== // Manacher演算法, Time: O(N), Space: O(N) // http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html public static String longestPalindromeManacher(String s) { String T = preProcess(s); int len = T.length(); // 經過變化後,len總是為奇數長 int[] P = new int[len]; // P陣列存放在某index下的迴文半徑長度 int C = 0, R = 0; // C為最長迴文子串的中心位置,R為當前最長迴文子串的右邊界位置 for(int i=1; i<len-1; i++) { int iMirror = C - (i-C); // 計算i的對應迴文左邊匹配位置i' /* if (R - i > P[iMirror]) P[i] = P[iMirror]; else // P[iMirror] >= R - i P[i] = R - i; // P[i] >= R - i,取最小值,之後再匹配更新。 可簡寫成P[i] = (R > i) ? Math.min(R-i, P[iMirror]) : 0; */ P[i] = (R > i) ? Math.min(R-i, P[iMirror]) : 0; // 貪心拓展以i為迴文中心的迴文子串 while(T.charAt(i+1+P[i]) == T.charAt(i-1-P[i])) { P[i]++; } // 如果以i為中心的迴文擴充套件超過了R,則我們找到一個新的更長迴文子串 // 因此 更新 最長迴文子串的中心和右邊界 if(P[i] > R-i) { C = i; R = i + P[i]; } } // 現在P[i]數組裡存放了以i為中心的迴文子串長度,用打擂臺方式找到最長者 int maxLen = 0; int centerIndex = 0; for(int i=1; i<len-1; i++) { if(P[i] > maxLen) { maxLen = P[i]; centerIndex = i; } } int start = (centerIndex-1-maxLen)/2; int end = start + maxLen; return s.substring(start, end); } // 把s轉換成T,如s="abba",則T="^#a#b#b#a#$" // ^和$加在字串首尾用來避免邊界檢查 private static String preProcess(String s) { int len = s.length(); if(len == 0) { return "^$"; } String ret = "^"; for(int i=0; i<len; i++) { ret += "#" + s.substring(i, i+1); } ret += "#$"; return ret; } public static void main(String[] args) { // String s = "abacdfgdcaba"; String s = "abcbcbb"; System.out.println(longestPalindromeBruteForce(s)); System.out.println(longestPalindromeDP1(s)); System.out.println(longestPalindromeExpand(s)); System.out.println(longestPalindromeManacher(s)); } }
最後:
這道題其實還可以用字尾樹(Suffix Tree)來做,但是複雜度(O(nlogn))超過Manacher演算法,並且實現起來更加麻煩,所以暫時沒新增進來。