難度中等
給定一個字串 s
,找到 s
中最長的迴文子串。你可以假設 s
的最大長度為 1000。
眾所周知,迴文子串是以某一軸為中心,左右呈映象對稱,如何找到 s 中最長的迴文子串呢?
暴力破解是最容易想到的一種解法,即以每個字元為中心,向左右兩邊擴,看擴出去的範圍有多大,同時記錄一個最大值。比如 s = "abhba"
,最大回文子串即以 字元 h 為中心,左右擴到底。然而這種方法有個問題,無法應對偶迴文,當 s ="abba"
的時候,以每個字元為中心擴結果得到的最長迴文子串長度為 1 !明顯是錯誤的!
所以為了解決上述無法應對偶迴文的情況,需要引入特殊字元來解決,即在每一個字元左右新增上一個特殊字元進行佔位,這樣就可以解決這個問題
上述的 s ="abba"
就變成了 s ="#a#b#b#a#"
,這樣我們就可以透過暴力法解決!
以下為暴力法的程式碼
public char[] getSPString(String s){
int len = s.length();
char[] arr = new char[2 * len + 1];
for(int i = 0 ;i < len;i++){
arr[2 * i] = '#';
arr[2 * i + 1] = s.charAt(i);
}
arr[2 * len] = '#';
return arr;
}
public String longestPalindrome(String s) {
if(s == null || s.length() <= 1){
return s;
}
char[] res = getSPString(s);
int len = res.length;
int C = 0; //最長迴文串中心
int max = 1; //最長迴文串長度
for(int i = 0; i< len;i++){
int j = 1;
while(i - j >=0 && j + i < len){
if(res[i - j] == res[i + j]){
j++;
}else{
break;
}
}
if(2 * j - 1 > max){
C = i;
max = 2 * j - 1;
}
}
return s.substring((C - max/2)/2 , (C+ max/2)/2);
}
暴力法的缺點在於,最壞情況下時間複雜度趨近於 O(n^2)!
而馬拉車演算法的關鍵在於透過定義一個最大右邊界,不斷地判斷當前位置地情況,然後尋找當前位置地對稱點,利用之前已經求出的資訊,加速整個判斷過程!注意:此過程也需要預先轉換成特殊字串進行處理!!!
首先我們定義幾個概念:
- 最大回文右邊界 R :即當前字串中的迴文子串達到的最右位置。例如 “#a#b#c#b#a#c#”,當到達第一個字元 ‘c’ 時,最大回文右邊界為下標10!
- 最大回文半徑 max_Radius 和 存放對應位置的最大回文半徑arr[ ] 陣列: 迴文半徑就是當前迴文中心到達右邊界或者左邊界的距離。如下圖所示
- 迴文中心 C :即當前最大右邊界R的情況下,迴文串的中心,與最大會問右邊界互為對應關係。
接下來就是加速過程!
- 當前位置 i 在最大回文右邊界
R
的外邊——以當前位置為中心往外擴,並更新R
和C
當遍歷到第一個字元’#’時,發現不能往外擴,即最大回文右邊界就是下標 0 ,然後碰到 字元 ‘a’的時候,發現 i 大於R
,故需要往外擴,然後發現能擴出去,即此時R
到了下標 2!C
也更新為了 i !
當前位置 i 在最大回文右邊界
R
的裡邊——判斷 i 的對稱點 i’ 的情況!
此時需要分三種情況:以 i’ 為中心的最大回文子串完全在最大左邊界即將最大右邊界
R
以C
為中心對稱過去)裡邊!如下圖,此時 i 位置的最大回文半徑 與 i’ 一樣,都為 2!此時時間複雜度為O(1)以 i’ 為中心的最大回文子串超出最大左邊界!如下圖所示,此時 i’ 的迴文串已經超出
L
到達了 左邊的 K 字元,那麼此時 i 的最大回文半徑就是R - i
,為什麼此時不用擴呢?其實可以證明,如果此時可以擴,那麼只有一種情況, 最右邊的不是 E 而是 K ,但是如果是 K 的話,此時違反了R
的最大回文右邊界 的定義!即R
一定至少大於等於最右邊 E 的位置!所以此時L
的左邊一個字元和R
右邊的字元一定不等! 此時時間複雜度為O(1)以 i’ 為中心的最大回文子串正好在最大左邊界上!即壓線!如下圖所示。此時以i位置為中心,以arr[i’]為半徑,需要再往外擴!因為這時不確定
R
右邊的字元是否滿足條件!
綜上,情況就是分為兩大類,i 在迴文右邊界裡邊和外邊!需要擴的兩種情況,即 i 在 R
外邊和壓線,都是在不斷地往右推動這個 R
,所以整個過程總起來看就是在不斷地往右推動著 R
前進,因為 R
最多能到最右邊,故整體的時間複雜度為 O(n)級別的!!!!!
下面直接上程式碼
public void manacher(String s) {
if (s == null || s.length() <= 1) {
return;
}
char[] sp = getSPString(s);
int len = sp.length;
int[] arr = new int[sp.length]; //最長迴文半徑陣列
int R = -1; //最大回文右邊界
int C = -1; //最大回文中心
int max = -1; // 最長迴文半徑
for (int i = 0; i < len; i++) {
arr[i] = R > i ? Math.min(arr[2 * C - i] , R - i) : 1;
while(i + arr[i] < len && i - arr[i] >= 0){
if(sp[i + arr[i]] == sp[i - arr[i]]){
arr[i] ++;
} else{
break;
}
}
if(i + arr[i] > R){
R = i +arr[i];
C = i;
}
if(max < arr[i]){
max = arr[i];
center = i;
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結