题目
给你一个字符串 s
,找到 s
中最长的 回文 子串。
示例
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
分析
动态规划法
使用动态规划来解决最长回文子串问题的核心思想是利用子问题的解来构建更大问题的解。具体来说,我们定义一个二维布尔数组 dp[i][j]
,其中 dp[i][j]
表示字符串 s
从索引 i
到索引 j
的子串是否为回文串。
状态定义
dp[i][j]
:表示子串 s[i...j]
是否为回文串,true
表示是,false
表示不是。
状态转移方程
当 s[i] == s[j]
时:
- 如果
j - i < 2
(即子串长度为 1 或 2),那么dp[i][j] = true
。例如,单个字符一定是回文串,两个相同字符组成的子串也是回文串。 - 如果
dp[i + 1][j - 1]
为true
,那么dp[i][j] = true
。也就是说,当两端字符相同,且去掉两端字符后的子串也是回文串时,当前子串就是回文串。
当 s[i] != s[j]
时,dp[i][j] = false
。
初始化
对于单个字符,即 i == j
时,dp[i][j] = true
,因为单个字符一定是回文串。
遍历顺序
由于状态转移方程依赖于 dp[i + 1][j - 1]
,所以我们需要按照子串长度从小到大的顺序进行遍历,即先遍历长度为 1 的子串,再遍历长度为 2 的子串,以此类推。
记录最长回文子串
在遍历过程中,记录下最长回文子串的起始位置和长度,最后根据这些信息截取最长回文子串。
时间复杂度:O(),
是字符串的长度
空间复杂度:O()
cpp
class Solution {
public:
string longestPalindrome(string s) {
int n = s.length();
if (n < 2) {
return s;
}
// 初始化 dp 数组
std::vector<std::vector<bool>> dp(n, std::vector<bool>(n, false));
int start = 0; // 最长回文子串的起始位置
int maxLen = 1; // 最长回文子串的长度
// 单个字符一定是回文串
for (int i = 0; i < n; ++i) {
dp[i][i] = true;
}
// 枚举子串长度
for (int len = 2; len <= n; ++len) {
// 枚举子串的起始位置
for (int i = 0; i <= n - len; ++i) {
int j = i + len - 1; // 子串的结束位置
if (s[i] == s[j]) {
if (len <= 2) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
} else {
dp[i][j] = false;
}
// 更新最长回文子串的信息
if (dp[i][j] && len > maxLen) {
start = i;
maxLen = len;
}
}
}
return s.substr(start, maxLen);
}
};
中心扩展法
可以使用中心扩展法来找出字符串 s
中最长的回文子串。该方法的核心思想是,回文串具有对称性,因此可以以每个字符或者每两个相邻字符为中心,向两边扩展来判断是否构成回文串。
具体步骤如下:
- 遍历字符串
s
中的每个字符,将其作为回文串的中心。 - 对于每个中心,分别考虑两种情况:
- 以单个字符为中心扩展,形成奇数长度的回文串。
- 以两个相邻字符为中心扩展,形成偶数长度的回文串。
- 在扩展过程中,不断比较左右字符是否相等,若相等则继续扩展,直到不相等为止。
- 记录每次扩展得到的回文串的长度和起始位置,最终找出最长的回文子串。
时间复杂度:O(),
是字符串的长度
空间复杂度:O(1)
cpp
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int start = 0, maxLen = 0;
// 遍历字符串中的每个字符
for (int i = 0; i < s.length(); ++i) {
// 以单个字符为中心扩展
int len1 = expandAroundCenter(s, i, i);
// 以两个相邻字符为中心扩展
int len2 = expandAroundCenter(s, i, i + 1);
// 取两种情况中的最大长度
int len = max(len1, len2);
if (len > maxLen) {
maxLen = len;
// 计算最长回文子串的起始位置
start = i - (len - 1) / 2;
}
}
// 截取最长回文子串
return s.substr(start, maxLen);
}
private:
// 中心扩展函数
int expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.length() && s[left] == s[right]) {
--left;
++right;
}
// 返回回文串的长度
return right - left - 1;
}
};