題目網址:https://leetcode.cn/problems/longest-palindromic-substring/
題意:給一 string s, 找出 s 中最長的迴文 substring。
注意:
- substring 指的是 string 中 連續的 subset
- subsequence 則是 string 的 subset

Solution 1:
想法:利用 DP, 因為我們發現「一個迴文去掉兩頭後, 剩下的部分仍為迴文」, 我們用 dp[i][j] 代表 substring s[i~j] 是否迴文, 則可得到下列公式:
dp[i][j] = (s[i] == s[j]) && dp[i + 1][j - 1]
dp[i + 1][j - 1] 在 dp[i][j] 的左下方, 故順序為由左至右, 一行一行的填, 其中對角線為 true

e.g. "abba" 中已知 "bb" 為迴文, 所以 dp[1][2] = true
➔ 則 dp[0][3] 則因為 "a" == "a" 且 dp[1][2] = true, 故 dp[0][3] = true, 代表 "abba" 為迴文
class Solution { public: string longestPalindrome(string s) { int start = 0, maxLen = 1; const int n = s.size(); vector<vector<bool>> dp(n, vector<bool> (n, true));
for (int j = 1; j < n; ++j) { for (int i = 0; i < j; ++i) { dp[i][j] = (s[i] == s[j]) && dp[i + 1][j - 1];
if (dp[i][j] && j - i + 1 > maxLen) { start = i; maxLen = j - i + 1; } } } return s.substr(start, maxLen); } };
|
- time:$O(n^2)$ ➔ for loop:$1+2+…+(n-1) = \dfrac{n(n-1)}{2}$ $= O(n^2)$
- space:$O(n^2)$ ➔
dp
Solution 2:
想法:利用中心擴散法, 也就是枚舉所有 substring 的中心位置, 總共枚舉 2n 個 substring, 因為每個位置 i 皆要考慮以其為中心的偶數 substring 和奇數 substring
更新 start 時, 之所以為 start = i - (maxLen - 1) / 2, 是因為我們的 i 是左中點, 而非右中點, 故 maxLen 要先減一再除 2
expand(s, left, right, n) 跳出迴圈時, 有兩種可能:
s[left] != s[right]:代表 s 中的 [left + 1, right - 1] 區間是回文 substring, 此時的回文substring 長度為 (right - 1) - (left + 1) - 1 = right - left - 1
第二種狀況是 left 或 right 越界, e.g. left = -1 且 right = n, 此時代表整個 s 為回文, 故回文長度為 n
➔ 第一種狀況得到的公式 right - left - 1 = n - (-1) - 1 = n 仍然滿足第二種狀況

class Solution { public: string longestPalindrome(string s) { int start = 0, maxLen = 1; const int n = s.size();
for (int i = 0; i < n; ++i) { const int curLen = max(expand(s, i, i, n), expand(s, i, i + 1, n)); if (curLen > maxLen) { maxLen = curLen; start = i - (maxLen - 1) / 2; } } return s.substr(start, maxLen); }
private: int expand(const string& s, int left, int right, const int n){ while (left >= 0 && right < n && s[left] == s[right]) { --left; ++right; }
return right - left - 1; } };
|
- time:$O(n^2)$ ➔ 遍歷每個 char, 每個 char 為中心最多往外擴散 $\dfrac{n}{2}$ 次, 得 $2 \cdot O(\dfrac{n}{2}) \cdot n$
- space:$O(1)$ ➔ 只需常數空間
Solution 3:(不會也沒差, 會前兩種方法就足夠了)
想法:利用 Manacher 演算法(可參考這篇), 其核心思想是結合 DP(回文特性)+ 中心擴散
class Solution { public: string longestPalindrome(string s) { string tmp = preProcess(s); const int n = tmp.size(); vector<int> p(n - 1, 0); int center = 0, right = 0;
for (int i = 1; i < n - 1; ++i) { int i_mirror = 2 * center - i;
if (i < right) { p[i] = min(p[i_mirror], right - i); } else { p[i] = 0; }
while (tmp.at(i - 1 - p[i]) == tmp.at(i + 1 + p[i])) { ++p[i]; }
if (i + p[i] > right) { center = i; right = i + p[i]; } }
int maxLen = 0, centerIndex = 0; for (int i = 1; i < n - 1; ++i) { if (p[i] > maxLen) { maxLen = p[i]; centerIndex = i; } }
const int start = (centerIndex - maxLen) / 2;
return s.substr(start, maxLen); }
private: string preProcess(const string& s){ string tmp = "^"; for (int i = 0; i < s.size(); ++i) { tmp += "#"; tmp += s[i]; } tmp += "#$"; return tmp; } };
|
- time:$O(n)$ ➔ 透過不斷拓展
right, 且 right 只增不減, 故只看 for loop, 因為 while loop 拜訪過的 char 不會再進 while loop(可透過 DP 快速得到)
- space:$O(n)$ ➔
tmp, p 的長度 = 2 * s.size() + 3