最长回文子串的几种求法
程序员文章站
2024-03-09 13:45:23
...
题目链接:最长回文子串
一、暴力求解
直接枚举两端点,再判断字符串是否是回文串,一共三重循环。
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;
}
};
其实还有种字符串哈希,然后用二分对每个字母去查找左右最长能形成回文串的边界,时间复杂度为,感兴趣的小伙伴们可以试试啦~