Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.html
Example 1:java
Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2:git
Input: "cbbd" Output: "bb"
這道題讓咱們求最長迴文子串,首先說下什麼是迴文串,就是正讀反讀都同樣的字符串,好比 "bob", "level", "noon" 等等。那麼最長迴文子串就是在一個字符串中的那個最長的迴文子串。LeetCode 中關於迴文串的題共有五道,除了這道,其餘的四道爲 Palindrome Number,Validate Palindrome,Palindrome Partitioning,Palindrome Partitioning II,咱們知道傳統的驗證迴文串的方法就是兩個兩個的對稱驗證是否相等,那麼對於找回文字串的問題,就要以每個字符爲中心,像兩邊擴散來尋找回文串,這個算法的時間複雜度是 O(n*n),能夠經過 OJ,就是要注意奇偶狀況,因爲迴文串的長度可奇可偶,好比 "bob" 是奇數形式的迴文,"noon" 就是偶數形式的迴文,兩種形式的迴文都要搜索,對於奇數形式的,咱們就從遍歷到的位置爲中心,向兩邊進行擴散,對於偶數狀況,咱們就把當前位置和下一個位置看成偶數行迴文的最中間兩個字符,而後向兩邊進行搜索,參見代碼以下:github
解法一:算法
class Solution { public: string longestPalindrome(string s) { if (s.size() < 2) return s; int n = s.size(), maxLen = 0, start = 0; for (int i = 0; i < n - 1; ++i) { searchPalindrome(s, i, i, start, maxLen); searchPalindrome(s, i, i + 1, start, maxLen); } return s.substr(start, maxLen); } void searchPalindrome(string s, int left, int right, int& start, int& maxLen) { while (left >= 0 && right < s.size() && s[left] == s[right]) { --left; ++right; } if (maxLen < right - left - 1) { start = left + 1; maxLen = right - left - 1; } } };
咱們也能夠不使用子函數,直接在一個函數中搞定,咱們仍是要定義兩個變量 start 和 maxLen,分別表示最長迴文子串的起點跟長度,在遍歷s中的字符的時候,咱們首先判斷剩餘的字符數是否小於等於 maxLen 的一半,是的話代表就算從當前到末尾到子串是半個迴文串,那麼整個迴文串長度最多也就是 maxLen,既然 maxLen 沒法再變長了,計算這些就沒有意義,直接在當前位置 break 掉就好了。不然就要繼續判斷,咱們用兩個變量 left 和 right 分別指向當前位置,而後咱們先要作的是向右遍歷跳太重複項,這個操做很必要,好比對於 noon,i在第一個o的位置,若是咱們以o爲最中心往兩邊擴散,是沒法獲得長度爲4的迴文串的,只有先跳太重複,此時left指向第一個o,right指向第二個o,而後再向兩邊擴散。而對於 bob,i在第一個o的位置時,沒法向右跳太重複,此時 left 和 right 同時指向o,再向兩邊擴散也是正確的,因此能夠同時處理奇數和偶數的迴文串,以後的操做就是更新 maxLen 和 start 了,跟上面的操做同樣,參見代碼以下: 數組
解法二:函數
class Solution { public: string longestPalindrome(string s) { if (s.size() < 2) return s; int n = s.size(), maxLen = 0, start = 0; for (int i = 0; i < n;) { if (n - i <= maxLen / 2) break; int left = i, right = i; while (right < n - 1 && s[right + 1] == s[right]) ++right; i = right + 1; while (right < n - 1 && left > 0 && s[right + 1] == s[left - 1]) { ++right; --left; } if (maxLen < right - left + 1) { maxLen = right - left + 1; start = left; } } return s.substr(start, maxLen); } };
此題還能夠用動態規劃 Dynamic Programming 來解,根 Palindrome Partitioning II 的解法很相似,咱們維護一個二維數組 dp,其中 dp[i][j] 表示字符串區間 [i, j] 是否爲迴文串,當 i = j 時,只有一個字符,確定是迴文串,若是 i = j + 1,說明是相鄰字符,此時須要判斷 s[i] 是否等於 s[j],若是i和j不相鄰,即 i - j >= 2 時,除了判斷 s[i] 和 s[j] 相等以外,dp[i + 1][j - 1] 若爲真,就是迴文串,經過以上分析,能夠寫出遞推式以下:post
dp[i, j] = 1 if i == jurl
= s[i] == s[j] if j = i + 1spa
= s[i] == s[j] && dp[i + 1][j - 1] if j > i + 1
這裏有個有趣的現象就是若是我把下面的代碼中的二維數組由 int 改成 vector<vector<int>> 後,就會超時,這說明 int 型的二維數組訪問執行速度完爆 std 的 vector 啊,因此之後儘量的仍是用最原始的數據類型吧。
解法三:
class Solution { public: string longestPalindrome(string s) { if (s.empty()) return ""; int n = s.size(), dp[n][n] = {0}, left = 0, len = 1; for (int i = 0; i < n; ++i) { dp[i][i] = 1; for (int j = 0; j < i; ++j) { dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1])); if (dp[j][i] && len < i - j + 1) { len = i - j + 1; left = j; } } } return s.substr(left, len); } };
最後要來的就是大名鼎鼎的馬拉車算法 Manacher's Algorithm,這個算法的神奇之處在於將時間複雜度提高到了 O(n) 這種逆天的地步,而算法自己也設計的很巧妙,很值得咱們掌握,參見我另外一篇專門介紹馬拉車算法的博客 Manacher's Algorithm 馬拉車算法,代碼實現以下:
解法四:
class Solution { public: string longestPalindrome(string s) { string t ="$#"; for (int i = 0; i < s.size(); ++i) { t += s[i]; t += '#'; } int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0; for (int i = 1; i < t.size(); ++i) { p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1; while (t[i + p[i]] == t[i - p[i]]) ++p[i]; if (mx < i + p[i]) { mx = i + p[i]; id = i; } if (resMx < p[i]) { resMx = p[i]; resId = i; } } return s.substr((resId - resMx) / 2, resMx - 1); } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/5
相似題目:
Longest Palindromic Subsequence
參考資料:
https://leetcode.com/problems/longest-palindromic-substring/