題目連接:https://leetcode.com/problems/longest-palindromic-substring/數組
題意很簡單,就是求一個字符串得最長子串,這裏的子串指連續的。spa
本文給出四個不一樣時間的解法。在LeetCode上的用時分別是500ms,250ms,60ms以及6ms。code
(1)500ms-最樸素解法blog
這種解法至關於模擬求解了,是一種正向思惟,即枚舉全部起點和終點,判斷長度是否最長,且是迴文的。時間複雜度O(n3).代碼以下:leetcode
1 class Solution { 2 public: 3 string longestPalindrome(string s) { 4 int maxlen = 0; 5 int len = s.length(); 6 string ans; 7 int r = 0,l = 0; 8 9 for(int i = 0; i < len; i++) 10 { 11 for(int j = len - 1; j > i; j--) 12 { 13 if (j - i + 1 <= maxlen) break; 14 bool flag = 0; 15 for(int k = 0; k < (j - i + 1) / 2; k++) 16 { 17 if(s[i + k] != s[j - k]) 18 { 19 flag = 1; 20 break; 21 } 22 } 23 if(flag == 0) 24 { 25 maxlen = j - i + 1; 26 l = i; 27 r = j; 28 } 29 } 30 } 31 return s.substr(l,r - l + 1); 32 } 33 };
(2)250ms----DP字符串
以空間換時間,定義一個布爾類型的數組p[][],負責存儲以 i、j 爲左右界的子串是否爲迴文的。而後再遍歷一遍,尋找是迴文且最長的。時間複雜度O(n2)。get
在求解 p 數組時,採用的策略是不斷擴大 p 的長度,易知:string
p[i][i] = true;io
p[i][i+1] = (s[i] == s[i+1]);class
那麼,當長度爲 k+1 時,有
p[i][i+k] = (p[i + 1][i + k - 1]) && (s[i] == s[i+k]);
從上式能夠看出,要求長度爲 k+1 時的迴文與否,就得知道長度爲 k 時的迴文與否。於是代碼以下:
1 class Solution { 2 public: 3 string longestPalindrome(string s) { 4 int maxlen = 0; 5 int len = s.length(); 6 int r = 0,l = 0; 7 bool p[1005][1005]; 8 for(int i = 0; i < len; i ++) 9 { 10 p[i][i] = true; 11 p[i][i+1] = s[i] == s[i+1]; 12 } 13 for(int k = 2; k < len; k++) 14 { 15 for(int i = 0; i < len && i + k < len; i++) 16 { 17 p[i][i+k] = s[i] == s[i+k] && p[i+1][i+k-1]; 18 } 19 } 20 for(int i = 0; i < len; i++) 21 for(int j = 0; j < len; j ++) 22 if(p[i][j] && j - i + 1 > maxlen) 23 { 24 l = i; 25 r = j; 26 maxlen = j - i + 1; 27 } 28 29 return s.substr(l,r - l + 1); 30 } 31 };
(3)60ms---中心擴展法
前面兩種都是正向思惟,後面這兩種是以解爲起點,逆向構造迴文子串。解法(3)最壞時間複雜度是O(n2),雖然時間複雜度與解法(2)是同樣的,可是逆向求解很大程度上減小了遍歷的次數,達不到迴文的基本條件就不會繼續擴展,在很大程度上下降了時間代價。
這種解法也可叫作從中間擴散(ExpandAroundCenter)。奇數子串:以 i 爲中心,向外擴展;偶數子串:以 (i,i+1)爲中心,向外擴展。擴展時知足:左右界不超出字符串邊界且對稱相等。以上述方法算出以 i 爲中心的奇/偶數子串中長的一個做爲當前的迴文子串長度temp,而後比較temp與maxlen,重複該步驟求出最終解。
其中,肯定了temp後,如何得到該子串的左右界須要考慮一下,舉個實例就算出來了:l = i - (temp - 1) / 2, r = temp - 1 + l; 其實 l 算出來了,根據 temp = r - l + 1 就能夠求出 r 了。
代碼以下:
1 class Solution { 2 public: 3 string longestPalindrome(string s) { 4 int maxlen = 0; 5 int len = s.length(); 6 int r = 0,l = 0; 7 for(int i = 0; i < len; i ++) 8 { 9 int temp = max(expandAroundCenter(s,i,i),expandAroundCenter(s,i,i+1)); 10 if(temp > maxlen) 11 { 12 l = i - (temp - 1) / 2; 13 r = temp - 1 + l; 14 maxlen = temp; 15 } 16 } 17 return s.substr(l,r - l + 1); 18 } 19 private: 20 int expandAroundCenter(string s, int l, int r) 21 { 22 while(l >= 0 && r < s.length() && s[l] == s[r]) 23 { 24 l--; 25 r++; 26 } 27 return r - l - 1; 28 } 29 };
(4)6ms---改進的中心擴展法
由解法(1)到解法(3)的過程當中能夠看出,縮短期的思想是不斷減小沒必要要的計算或重複的計算。解法(4)就是在解法(3)的基礎上進一步縮減重複的計算。縮減的策略是,不是以一個字符 i 爲中心計算其奇數或偶數長度的迴文子串,而是以一個由相同字符組成的連續子串爲中心向外擴展。仔細想一想實際上是頗有道理的。字符個數總共只有128個,當字符串長度很大時,構成迴文子串的字符中最有多是重複的字符反覆出現。從最終的運行時間可能看出這種策略的優點,比解法(3)平均縮短了10倍的時間。
代碼以下:
1 class Solution { 2 public: 3 std::string longestPalindrome(std::string s) { 4 if (s.size() < 2) 5 return s; 6 int len = s.size(), max_left = 0, max_len = 1, left, right; 7 for (int start = 0; start < len && len - start > max_len / 2;) { 8 left = right = start; 9 while (right < len - 1 && s[right + 1] == s[right])//求出重複字符組成的子串的右界 10 ++right; 11 start = right + 1;//下一輪遍歷的起點。 12 while (right < len - 1 && left > 0 && s[right + 1] == s[left - 1]) {//不斷向外擴展 13 ++right; 14 --left; 15 } 16 if (max_len < right - left + 1) { 17 max_left = left; 18 max_len = right - left + 1; 19 } 20 } 21 return s.substr(max_left, max_len); 22 } 23 };
總結:主動尋找構造解的方式 比 被動搜索解的方式 效率更高!