LeetCode5. Longest Palindromic Substring 最長迴文子串 4種方法

題目連接: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 };

 

總結:主動尋找構造解的方式 比 被動搜索解的方式 效率更高!

相關文章
相關標籤/搜索