這是一道非常經典的字符串處理問題,迴文是指該字符串是對稱的,如abcdcba, aabbaa
該問題最樸素的解法是找到所有子串,判斷是不是迴文並找出最長的迴文子串,代碼很簡單,這裏就不寫了;
第二種解法是中心點法,即以某個點爲中心,用兩個索引分別往前和往後走,找出其迴文,這裏需要注意迴文是奇數個字符還是偶數個字符,即要處理兩種情況;
第三種解法是最有效的解法,時間複雜度比以上兩種方法都低O(N),該方法被稱爲Manacher算法,下面介紹該算法的思路:
首先該算法採用了一個非常巧妙的方式將所有字符串的長度轉換成奇數,即通過在字符串前面、字符串後面及各個字符之間添加一個特殊字符(如‘#’,‘$’),以字符串abcdedcbf爲例,處理後的字符串s = "#a#b#b#d#e#d#b#b#f#";
然後,建立一個輔助數組p,p中元素p[i]存儲的是以s[i]爲中心的迴文半徑,如下所示:
s # a # b # b # d # e # d # b # b # f #
p 1 2 1 2 3 2 1 2 1 8 1 2 1 2 3 2 1 2 1
這個算法的關鍵點在於p[i]的計算,p[i]是通過兩個輔助變量id,mx來求解的,mx代表能到達最右邊的迴文的結尾,id代表該回文的中心點;當mx > i 時,則p[i] = min{ mx-i , p[2*id - i] };這樣可能比較難理解,我們用圖表示出這兩種情況會比較容易理解,看明白圖後就會發現很簡單:
2*id - i是i關於id的對稱點,由於mx > i,所以i到mx之間的字符,mx的對稱點到j之間的字符對稱,如果以s[j]爲中心的迴文還沒延伸到mx的對稱點,因爲i和j對稱,所以以s[i]爲中心的迴文長度就等於p[j],即p[2*id-i];
當mx-i < p[j]時,此時我們可以先將p[i]指定爲mx-i,然後再判斷s[mx]之後的字符是否與前面的字符對稱,該判斷過程與另一種情況相同,即mx <= i時,此時也需要一個個字符判斷;
該算法的代碼如下:
public String longestPalindrome(String s) {
if(s == null || s.length() == 0)
return "";
StringBuilder sb = new StringBuilder("#");
for(int i = 0; i < s.length(); i++){
sb.append(s.charAt(i)).append('#');
}
String str = sb.toString();
int[] p = new int[str.length()];
int id = -1;
int mx = 0;
int max = 0;
for(int i = 0; i < str.length(); i++){
if(i < mx){
p[i] = Math.min(mx-i, p[2*id-i]);
}else{
p[i] = 1;
}
while(i+p[i] < str.length() && i-p[i] >= 0 && str.charAt(i+p[i]) == str.charAt(i-p[i]))
p[i]++;
if(i+p[i] > mx){
id = i;
mx = i+p[i];
}
if(p[i] > p[max]){
max = i;
}
}
StringBuilder result = new StringBuilder();
for(int i = max-p[max]+1; i <= max+p[max]-1; i++){
if(str.charAt(i) != '#')
result.append(str.charAt(i));
}
return result.toString();
}
詳見LeetCode:https://oj.leetcode.com/problems/longest-palindromic-substring/