Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
求字符串的最長回文子串
算法1:暴力解法,枚舉所有子串,對每個子串判斷是否為回文,復雜度為O(n^3)
算法2:刪除暴力解法中有很多重復的判斷。很容易想到動態規划,時間復雜度O(n^2),空間O(n^2),動態規划方程如下:
- dp[i][j] 表示子串s[i…j]是否是回文
- 初始化:dp[i][i] = true (0 <= i <= n-1); dp[i][i-1] = true (1 <= i <= n-1); 其余的初始化為false
- dp[i][j] = (s[i] == s[j] && dp[i+1][j-1] == true)
在動態規划中保存最長回文的長度及起點即可
class Solution { public: string longestPalindrome(string s) { const int len = s.size(); if(len <= 1)return s; bool dp[len][len];//dp[i][j]表示s[i..j]是否是回文 memset(dp, 0, sizeof(dp)); int resLeft = 0, resRight = 0; dp[0][0] = true; for(int i = 1; i < len; i++) { dp[i][i] = true; dp[i][i-1] = true;//這個初始化容易忽略,當k=2時要用到 } for(int k = 2; k <= len; k++)//枚舉子串長度 for(int i = 0; i <= len-k; i++)//枚舉子串起始位置 { if(s[i] == s[i+k-1] && dp[i+1][i+k-2]) { dp[i][i+k-1] = true; if(resRight-resLeft+1 < k) { resLeft = i; resRight = i+k-1; } } } return s.substr(resLeft, resRight-resLeft+1); } };
算法3:以某個元素為中心,分別計算偶數長度的回文最大長度和奇數長度的回文最大長度。時間復雜度O(n^2),空間O(1)
class Solution { public: string longestPalindrome(string s) { const int len = s.size(); if(len <= 1)return s; int start, maxLen = 0; for(int i = 1; i < len; i++) { //尋找以i-1,i為中點偶數長度的回文 int low = i-1, high = i; while(low >= 0 && high < len && s[low] == s[high]) { low--; high++; } if(high - low - 1 > maxLen) { maxLen = high - low -1; start = low + 1; } //尋找以i為中心的奇數長度的回文 low = i- 1; high = i + 1; while(low >= 0 && high < len && s[low] == s[high]) { low--; high++; } if(high - low - 1 > maxLen) { maxLen = high - low -1; start = low + 1; } } return s.substr(start, maxLen); } };
算法4:Manacher算法,時間復雜度O(n), 空間復雜度O(n)
該算法首先對字符串進行預處理,在字符串的每個字符前后都加入一個特殊符號,比如字符串 abcd 處理成 #a#b#c#d#,為了避免處理越界,在字符串首尾加上不同的兩個特殊字符(c類型的字符串尾部不用加,因為自帶‘\0’),這樣預處理后最終變成$#a#b#c#d#^,經過這樣處理后有個好處是原來的偶數長度和奇數長度的回文在處理后的字符串中都是奇數長度。假設處理后的字符串為s 本文地址
對於已經預處理好的字符串我們用數組p[i]來記錄以字符S[i]為中心的最長回文子串向左/右擴張的長度(包括S[i]),以字符串“12212321”為例,p數組如下
s: $ # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 # ^
p: 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1
可以看出,P[i]-1正好是原字符串中回文串的總長度, 如果p數組已知,遍歷p數組找到最大的p[i]就可以求出最長回文的長度,也可以求出回文的位置
下面給出求p[]數組的方法:
設id是當前求得的最長回文子串中心的位置,mx為當前最長回文子串的右邊界(回文子串不包括該右邊界),即mx = id + p[id]。記j = 2*id – i ,即 j 是 i 關於 id 的對稱點。
1、 當i < mx 時,如下圖。此時可以得出一個非常神奇的結論p[i] >= min(p[2*id - i], mx - i),下面我們來解釋這個結論
如何根據p[j]來求p[i]呢,又要分成兩種情況
(1.1)當mx – i > p[j], 這時候以S[j]為中心的回文子串包含在以S[id]為中心的回文子串中,由於 i 和 j 對稱,以S[i]為中心的回文子串必然包含在以S[id]為中心的回文子串中,所以 P[i] 至少等於 p[j], 后面的再繼續匹配。如下圖
注:這里其實p[i]一定等於p[j],后面不用再匹配了。因為如果p[i]后面還可以繼續匹配,根據對稱性,p[j]也可以繼續擴展了
(1.2)當mx – i <= p[j], 以S[j]為中心的回文子串不完全包含於以S[id]為中心的回文子串中,但是基於對稱性可知,下圖中兩個綠框所包圍的部分是相同的,也就是說以S[i]為中心的回文子串,其向右至少會擴張到mx的位置,也就是說 P[i] 至少等於 mx - i,至於mx之后的部分是否對稱,就只能老老實實去匹配了。
注:如果mx – i < p[j] ,這時p[i]一定等於mx - i, 因為如果p[i]在mx之后還可以繼續匹配,根據對稱性,mx之后匹配的點(包括mx)一定會出現在my的前面,這說明p[id]也可以繼續擴展了
2、當i >= mx, 無法對p[i]做更多的假設,只能p[i] = 1,然后再去匹配
算法復雜度分析:根據斜體字部分的注釋,只有當mx-i = p[j]時 以及 i > mx時才要擴展比較,而mx也是在不斷擴展的,總體而言每個元素比較次數是n的線性關系,所以時間復雜度為O(n)
class Solution { public: string longestPalindrome(string s) { const int len = s.size(); if(len <= 1)return s; //Manncher算法 ,o(n) string str = preProcess(s); int n = str.size(), id = 0, mx = 0; vector<int>p(n, 0); for(int i = 1; i < n-1; i++) { p[i] = mx > i ? min(p[2*id-i], mx-i) : 1; //if(mx <= i || (mx > i && p[2*id-i] == mx - i)) //根據正文斜體部分的注釋,這里可要可不要 while(str[i+p[i]] == str[i-p[i]])p[i]++; if(i + p[i] > mx) { mx = i + p[i]; id = i; } } //遍歷p,尋找最大回文長度 int maxLen = 0, index = 0; for(int i = 1; i < n-1; i++) if(p[i] > maxLen) { maxLen = p[i]; index = i; } return s.substr((index - maxLen)/2, maxLen-1); } //預處理字符串,abc預處理后變成$#a#b#c#^ string preProcess(const string &s) { int n = s.size(); string res; res.push_back('$');//把$放到字符串頭部 res.push_back('#');//以#作為原來字符串中每個字符的間隔 for(int i = 0; i < n; i++) { res.push_back(s[i]); res.push_back('#'); } res.push_back('^');//以^作為字符串的結尾 return res; } };
算法5:可以用后綴數組來解,在源字符串后面加一個特殊字符,然后把源字符串的反轉串連接到源字符串后面,那么問題就變成了求這個新字符串的某兩個后綴的最長公共前綴,該問題是典型的后綴數組應用,可以參考here,關於后綴數組請關注我的后續博客
參考資料:
Longest Palindromic Substring Part II
Manacher's ALGORITHM: O(n)時間求字符串的最長回文子串
Longest Palindromic Substring | Set 2
【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3675788.html