LeetCode-最長迴文子串
Table of Contents
1 Medium-最長迴文子串
給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲1000。 php
1.1 示例 1:
輸入: "babad" 輸出: "bab" css
注意: "aba"也是一個有效答案。 html
1.2 示例 2:
輸入: "cbbd" 輸出: "bb" java
2 本身的解答
2.1 思路
- 遍歷字符串,而後以當前索引爲中心向兩邊擴展,判斷是否爲迴文.
- 是迴文,記錄左邊界和長度,最後返回子串便可.
2.2 代碼
class Solution { public String longestPalindrome(String s) { if (s.length() < 1) { return ""; } // 用做遍歷字符串s int i; // 用來記錄折半位置 int len; int maxlen = 1; int ms = 0; // 奇數的迴文子串 for (i = 0; i < s.length(); i++) { int j = i + 1; int k = i - 1; while (j < s.length() && k >= 0 && s.charAt(j) == s.charAt(k)) { len = j - k + 1; if (len > maxlen) { // 有更長的迴文串,記錄起始位置 maxlen = len; // 迴文從k開始 ms = k; } j++; k--; } } // 偶數的迴文子串 for (i = 0; i < s.length(); i++) { int k = i; int j = i + 1; while (j < s.length() && k >= 0 && s.charAt(k) == s.charAt(j)) { len = j - k + 1; if (len > maxlen) { maxlen = len; // 迴文從k開始 ms = k; } k--; j++; } } return s.substring(ms, ms + maxlen); } }
3 官方解答
3.1 方法一:最長公共子串
3.1.1 常見錯誤
- 有些人會忍不住提出一個快速的解決方案,不幸的是,這個解決方案有缺陷(可是能夠很容易地糾正):
- 反轉
S
,使之變成S'
。找到S
和S'
之間最長的公共子串,這也必然是最長的迴文子串。這彷佛是可行的,讓咱們看看下面的一些例子。 - 例如,
S = 「caba」
, S' =「abac」
S
以及S′
之間的最長公共子串爲「aba」
,偏偏是答案。- 讓咱們嘗試一下這個例子:
S=「abacdfgdcaba」
,S′=「abacdgfdcaba」
:S
以及S′
之間的最長公共子串爲「abacd」
,顯然,這不是迴文。
3.2 算法
咱們能夠看到,當 S
的其餘部分中存在非迴文子串的反向副本時,最長公共子串法就會失敗。爲了糾正這一點,每當咱們找到最長的公共子串的候選項時,都須要檢查子串的索引是否與反向子串的原始索引相同。若是相同,那麼咱們嘗試更新目前爲止找到的最長迴文子串;若是不是,咱們就跳過這個候選項並繼續尋找下一個候選。 這給咱們提供了一個複雜度爲 \(O(n^2)\) 動態規劃解法,它將佔用 \(O(n^2)\) 的空間(能夠改進爲使用 \(O(n)\) 的空間)。 python
3.3 官方解答方法二:暴力法
很明顯,暴力法將選出全部子字符串可能的開始和結束位置,並檢驗它是否是迴文。 算法
3.3.1 複雜度分析
- 時間複雜度:\(O(n^3)\) ,假設
n
是輸入字符串的長度,則 \(\binom{n}{2} = \frac{n(n-1)}{2}\) 爲此類子字符串(不包括字符自己是迴文的通常解法)的總數。由於驗證每一個子字符串須要 \(O(n)\) 的時間,因此運行時間複雜度是 \(O(n^3)\) 。 - 空間複雜度:\(O(1)\) 。
3.4 官方解答方法三:動態規劃
爲了改進暴力法,咱們首先觀察如何避免在驗證迴文時進行沒必要要的重複計算。考慮 「ababa」
這個示例。若是咱們已經知道 「bab」
是迴文,那麼很明顯, 「ababa」
必定是迴文,由於它的左首字母和右尾字母是相同的。 sql
咱們給出 P(i,j)
的定義以下: shell
\[ P(i,j) = \begin{cases} \text{true,} &\quad\text{若是子串} S_i \dots S_j \text{是迴文子串}\\ \text{false,} &\quad\text{其它狀況} \end{cases} \] sass
所以, ruby
\[ P(i, j) = ( P(i+1, j-1) \text{ and } S_i == S_j ) \]
基本示例以下:
\[ P(i, i) = true \]
\[ P(i, i+1) = ( S_i == S_{i+1} ) \]
這產生了一個直觀的動態規劃解法,咱們首先初始化一字母和二字母的迴文,而後找到全部三字母迴文,並依此類推…
3.4.1 複雜度分析
- 時間複雜度: \(O(n^2)\) , 這裏給出咱們的運行時間複雜度爲 \(O(n^2)\) 。
- 空間複雜度: \(O(n^2)\) , 該方法使用 \(O(n^2)\) 的空間來存儲表。
3.5 官方解答方法四:中心擴展算法
事實上,只需使用恆定的空間,咱們就能夠在 \(O(n^2)\) 的時間內解決這個問題。
咱們觀察到迴文中心的兩側互爲鏡像。所以,迴文能夠從它的中心展開,而且只有 \(2n - 1\) 個這樣的中心。
你可能會問,爲何會是 \(2n−1\) 個,而不是 \(n\) 箇中心?緣由在於所含字母數爲偶數的迴文的中心能夠處於兩字母之間(例如 「abba」
的中心在兩個 ‘b’
之間)。
3.5.1 複雜度分析
- 時間複雜度:\(O(n^2)\) , 因爲圍繞中心來擴展迴文會耗去 \(O(n)\) 的時間,因此總的複雜度爲 \(O(n^2)\) 。
- 空間複雜度: \(O(1)\) 。
3.6 官方解答方法五:Manacher 算法
還有一個複雜度爲 \(O(n)\) 的 Manacher 算法,你能夠在這裏找到詳盡的解釋。然而,這是一個非同尋常的算法,在45分鐘的編碼時間內提出這個算法將會是一個徹徹底底的挑戰。可是,請繼續閱讀並理解它,我保證這將是很是有趣的。
3.6.1 代碼
class Solution { /** * Transform S to T * For example, S = "abba", T="^#a#b#b#a#$". * ^ and $ signs are sentinels appended to each end to avoid bounds check */ public String preProcess(String s) { int n = s.length(); if (n == 0) { return "^$"; } StringBuilder ret = new StringBuilder("^"); for (int i = 0; i < n; i++) { ret.append("#").append(s.charAt(i)); } ret.append("#$"); return ret.toString(); } public String longestPalindrome(String s) { String T = preProcess(s); int n = T.length(); int[] P = new int[n]; // Center point int C = 0; // Right border int R = 0; // first character is $ for (int i = 1; i < n - 1; i++) { // equals to i' = C - (i - C) int iMirror = 2 * C - i; P[i] = (R > i) ? Math.min(R-i, P[iMirror]) : 0; // Attempt to expand palindrome centered at i while(T[i + 1 + P[i]] == T[i - 1 - P[i]]) { P[i]++; } // if palindrome centered at 1 expand past R, // adjust center based on expanded palindrome. if (i + P[i] > R) { C = i; R = i + P[i]; } } // Find the maximum element in P. int maxLen = 0; int centerIndex = 0; for (int i = 1; i < n - 1; i++) { if (P[i] > maxLen) { maxLen = P[i]; centerIndex = i; } } return s.substring((centerIndex - 1 - maxLen) / 2, maxLen); } }