5. 【中等】最长回文子串
https://leetcode-cn.com/problems/longest-palindromic-substring/
给你一个字符串s,找到s中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
示例 3:
输入:s = "a"
输出:"a"
示例 4:
输入:s = "ac"
输出:"a"
提示:
1 <= s.length <= 1000
s仅由数字和英文字母(大写和/或小写)组成
【解】
【方法1】动态规划(DP)。
设
P(i, j)={█(“true” &, s[i, j]“是回文串” @“false” &, s[i, j]“不是回文串,或不合法(” i>j")" )┤
若字符串s的某个子串s[i, j]是回文串,且最邻近它两端的字符均相等,即s[i-1]==s[j+1],则字符串s[i-1, j+1]也是回文串。因此,状态转移方程
{█(P(i, j)&=P(i+1, j-1)∧s[i]==s[j]@P(i, i)&=“true” @P(i, i+1)&=s[i]==s[i+1] )┤
易得边界条件:长为1的字符串本身就是其最长回文子串;长为2的字符串,要么它本身为它的最长回文子串,要么它没有任何回文子串。
实现的时候要注意,一个字符串s的长度大于2的子串s[i, j]是否为s的最长回文子串,需要凭借之前已经判定过的长度更短的字串的判定结果来做出判定。
代码:
#include <vector>
class Solution {
public:
string longestPalindrome(const string& s) {
if (s.size() < 2) return s; // 长度为 1 的字符串本身就是其最长回文子串
const size_t n = s.size(); // 字符串 s 的长度记为 n
size_t lmax = 1; // 最大长度
size_t lp_begin = 0; // 一个最长回文子串的起始位置
vector<vector<bool>> p(n, vector<bool>(n)); // p[i][j] 表示:字符串 s[i, j] 是否为回文串
for (size_t i = 0; i < n; ++i) p[i][i] = true; // 长为 1 的全部子串都是回文串
for (size_t l = 2; l <= n; ++l) { // 枚举长为 l = 2, 3, ..., n 的子串
for (size_t i = 0, j; i < n; ++i) { // 枚举长为 l 的子串的全部可能出现的位置
j = l + i - 1;
if (j >= n) break; // 下标越界,字符串非法
if (s[i] != s[j]) p[i][j] = false;
else {
if (j - i < 3) p[i][j] = true; // 正在考察的字符串 s[i, j] 长为 1 或 2,且 s[i] == s[j],它应当是回文串
else p[i][j] = p[i + 1][j - 1]; // 根据之前枚举过的长度更短的字符串,判定本字符串 s[i, j] 是否为回文串
}
if (p[i][j] && j - i + 1 > lmax) { // 更新最长回文子串的统计数据
lmax = j - i + 1;
lp_begin = i;
}
}
}
return s.substr(lp_begin, lmax); // 给出一个解
}
};
用时:584 ms,短于26.94%的用户;内存占用:29.2 MB,少于55.93%的用户。
【方法2】从回文中心向两端扩展
枚举全部长度为1或2的子串,不断考察分别与子串两端相邻的2个字符。若添加这2个字符后,新子串仍为回文串,则继续如此添加下去,直到不能再添加为止。
代码:
#include <vector>
class Solution {
pair<ptrdiff_t, ptrdiff_t> ExpandFromPalindromicCenter(const string& S, ptrdiff_t L, ptrdiff_t R) {
while (L >= 0 and R < S.size() and S[L] == S[R]) { --L; ++R; }
return { L + 1, R - 1 };
}
public:
string longestPalindrome(const string& s) {
ptrdiff_t L = 0, R = 0;
for (ptrdiff_t i = 0; i < s.size(); ++i) {
auto [L1, R1] = ExpandFromPalindromicCenter(s, i, i); // 回文中心为一个字符的情况
auto [L2, R2] = ExpandFromPalindromicCenter(s, i, i + 1); // 回文中心为两个字符的情况
if (R1 - L1 > R - L) { L = L1; R = R1; }
if (R2 - L2 > R - L) { L = L2; R = R2; }
}
return s.substr(L, R - L + 1);
}
};
用时:16 ms,优于92.08%的用户;内存占用:6.9 MB,优于95.08%的用户。
【方法3】Manacher算法
(待填坑)