題目鏈接:最長迴文子串
一、暴力求解
直接枚舉兩端點,再判斷字符串是否是迴文串,一共三重循環。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.length();
pair<int, int> p = {0, 0}; // 記錄迴文串起始位置和長度
for(int i = 0; i < n; i++)
{
if(p.second == n) break; // 整個字符串都是迴文串,可以直接退出
for(int j = i; j < n; j++)
{
int l = i, r = j;
while(l <= r && s[l]==s[r]) l++, r--;
if(l >= r && j-i+1 > p.second) p = {i, j-i+1};
}
}
return s.substr(p.first, p.second);
}
};
這個暴力的空間複雜度爲。
二、優化的暴力
從每個字符出發,沿左右分別前進,直到左右兩邊字符不同時退出。舉個例子,s="dabacd"
,從第三個字母b
出發,向左和向右走一步都得到a
,表明當前aba
是迴文串,繼續向左走得到d
,向右走得到c
,不同,表示dabac
不是迴文串,結束循環。可以得到以第三個字母b
爲中心的最長迴文串爲aba
。上述情況爲最長迴文串長度爲奇數的情況,如果是s"baabd"
這種情況,最長迴文串爲baab
,長度爲奇數,可以在每兩個字符間插入一個額外字符t="b#a#a#b#c"
,這樣兩種情況統一。
class Solution {
public:
string longestPalindrome(string s) {
string t = "";
for(int i = 0; i < s.length(); i++)
t += s[i], t += '#';
int n = t.length() - 1;
string res = "";
for(int i = 0; i < n; i++)
{
// 不可能找到比當前更長的最長迴文串,提前break
if(res.length() > n-i) break;
string temp = "";
int l = i, r = i;
while(l>=0 && r<n && t[l]==t[r]) l--, r++;
for(int j = l+1; j <= r-1; j++)
if(t[j]!='#') temp += t[j];
if(temp.length() > res.length()) res = temp;
}
return res;
}
};
三、動態規劃
p[i][j]
表示字符串從i到j是否是迴文串,p[i][j]
初始化爲false
,當且僅當s[i]==s[j]&&j-i<3
或者s[i]==s[j]&&p[i+1][j-1]==true
時p[i][j]=true
。因爲判斷p[i][j]
的狀態需要知道p[i+1][j]
的狀態,所以我們第一重遍歷i
需要從大到小進行。同時記錄迴文串起始位置和長度信息以便更新。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.length();
if(n<2) return s;
bool dp[n][n] = {false};
pair<int, int> p = {0, 0}; // 記錄迴文串起始位置和長度
for(int i = n-1; i >= 0; i--)
for(int j = i; j < n; j++)
{
if(s[i]==s[j] && (j-i<3 || dp[i+1][j-1]==true))
{
dp[i][j] = true;
if(j-i+1 > p.second) p = {i, j-i+1};
}
}
return s.substr(p.first, p.second);
}
};
四、manacher算法
跟第二種方法類似,只是我們統計以某個字母爲中心的最長迴文串時採取更加高效的措施,這就是“馬拉車”算法。
struct P{
int l, r, len; // 起點,終點,實際長度
};
class Solution {
public:
string longestPalindrome(string s) {
if(s.length() < 2) return s;
string t;
for(auto c: s) t += c, t += '#';
int n = t.length() - 1;
int d[n] = {0};
P p = {0, 0, 0}; // {開始位置,長度}
for(int i = 0, l = 0, r = -1; i < n; i++)
{
int k = (i > r) ? 1 : min(d[l + r - i], r - i);
while(0 <= i - k && i + k < n && t[i - k] == t[i + k]) k++;
d[i] = k--;
int start = i - d[i] + 1;
int len = t[start] == '#' ? (d[i] * 2 - 2) / 2 : (d[i] * 2) / 2;
if(len > p.len) p = {start, i + d[i] - 1, len};
if(i + k > r) l = i - k, r = i + k;
}
string res = "";
for(int i = p.l; i <= p.r; i++) if(t[i] != '#') res += t[i];
return res;
}
};
其實還有種字符串哈希,然後用二分對每個字母去查找左右最長能形成迴文串的邊界,時間複雜度爲,感興趣的小夥伴們可以試試啦~