找到最長迴文字串 - Manacher's Algorithm

殷老實發表於2016-10-03

記得剛開始學習計算機的時候,除了輸出星星就是找到最長迴文字串這樣的問題, 開始以為最長迴文串問題很簡單,但是經過多年的學習再回頭看的時候發現,它並不簡單,今天就給它解個密。


開始我們先來看一個時間複雜度差的演算法。 


1. 時間複雜度為 n^3.    空間複雜度為 1.  這個演算法基本上是最簡單的了,最好理解的。

/**
	 * 使用iteration
	 * 迴圈三次
	 * 	 	第一次(最外邊迴圈 : 從最長的長度開始,迴圈長度,每次減1
	 * 		第二次 (中間迴圈): 對每個長度,迴圈最長字串的起始點,起點從0每次加1
	 * 		第三次 (最內迴圈): 對每個長度每個起點,對左右進行比較,每次左+1,右-1
	 * 
	 * 
	 * 
	 * 
	 * */
	public static String longestP (String S) {
		if (S.length() < 2 || S == null) { return S; }
		int length = S.length();
		int left = 0;
		int right = 0;
		while (length >= 0) {
			
			for (int i = 0; i + length - 1 < S.length(); i++) {
				left = i;
				right = i + length - 1;
				
					while (left < right) {
						if (S.charAt(left) == S.charAt(right)) {
							left ++;
							right --;
							continue;
						} else {
							break;
						}
					}
					
					if (left >= right) { return S.substring(i, i+ length); }
			}
			
			length --;
		}
		
		return "";
	}


2. Manacher's Algorithm - 時間複雜度為 N ( 應該大於N 小於 N ^ 2)

這個演算法需要一個預處理主要的三個變數


預處理: 將輸入字串的各個字元使用特殊字元進行 分割,比如 #。   e.g.  input: abba 處理: #a#b#b#a#

三個主要變數: 

1. 一個和預處理之後大小一樣的 int 陣列 rad[] - 用來儲存以當前字元為中心的最長子迴文字串的半徑。

2. 一個整形變數來儲存- 我們遍歷過的所有子迴文字串能觸及到的最右邊的位置的maxRight。

3. 另一個整形變數來儲存 - 2.中maxRight所對應的 子迴文字串的中心點的位置pos。


每次遍歷從以當前字元為中心向兩頭擴充套件,擴充套件的半徑是多少?

這就是個問題。。。


解決辦法, 通過對比 i 和 maxRight, 當i < maxRight 的時候, 判斷 maxRight - i 的大小 和  一個點 j ( 這個點是 i 以 pos為對稱的  對稱點 )的最長迴文半徑的大小(儲存在rad 中). 

使用較小值作為半徑, 從當前點開始進行兩頭擴張。每次擴張成功之後半徑+1。


public static String Manacher (String S) {
		if (S.length() < 2 || S == null) { return S; }
		
		//construct new string
		//basically, insert one # to the String S,
		//make sure each character is surrounded by #
		StringBuilder newS = new StringBuilder();
		newS.append("#");
		
		int start = 0;
		while (start < S.length()) {
			newS.append(S.charAt(start));
			newS.append("#");
			
			start ++;
		}
		
		int [] rad = new int[newS.length()];	//to store the radius of a string with pivot of current node
		int maxRight = -1;	//most right we can touch.
		int pos = -1;		//the position for which node that can touch the most right.
		
		for (int i = 0; i < newS.length(); i++) {
			int r = 1; 	//radius 
			
			if (i <= maxRight) {
				//since i is less than the max right,
				//so we can compare the radius of the node which is symmetric with pos and centered on current node
				r = Math.min(rad[2*pos - i], maxRight - i);
			}
			
			//extend the string, compare with the i - r(left) and i + r (right)
			//if they equal, so that radius ++
			while (i - r >= 0 && i + r < newS.length() 
					&& newS.charAt(i-r) == newS.charAt(i+r)) {
				r++;
			}

			//if i + r -1 is greater than the most right that the most right we can touch.
			//we have to update the max right and the position of max right
			if (i + r - 1 > maxRight) {
				maxRight = i + r - 1;
				pos = i;
			}
			
			rad[i] = r;
		}
		int MaxR = 0;
		int pivot = 0;
		for (int i = 0; i < rad.length; i++) {
			if (rad[i] > MaxR) {
				MaxR = rad[i];
				pivot = i;
			}
			
		}
		return newS.substring(pivot - MaxR + 1, pivot + MaxR).replace("#", "");
	}


有問題歡迎指出。

相關文章