1. 程式人生 > >總結最長迴文子串的幾種做法 Longest Palindrome Substring

總結最長迴文子串的幾種做法 Longest Palindrome Substring

題目是:找出一個字串中的最長迴文子串。

例如: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演算法,並且實現起來更加麻煩,所以暫時沒新增進來。