一.问题描述
给你一个字符串 s
,找到 s
中最长的回文子串(如果字符串向前和向后读都相同,则它满足 回文性。)。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
-
1 <= s.length <= 1000
-
s
仅由数字和英文字母组成
二.问题思路
2.1暴力搜索(从两边向中间收缩)
比较容易想到的算法是暴力搜索算法,对于每个可能的子串长度,从最长到最短,检查是否存在这样的回文子串。一旦找到,立即返回。
具体步骤
1. 外层循环从字符串长度开始,逐渐减少到1。
2. 对于每个长度l,从0到n-l的位置开始,检查子串s[i..i+l-1]是否是回文。
3. 一旦找到第一个符合条件的回文子串,立即返回,因为它是最长的。
不过,这种方法的缺点是对于每个长度len,可能需要检查n-len+1个子串,每个子串需要O(len)的时间来判断是否是回文。总的时间复杂度是O(n³)。
代码实现
#include <string>
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if (n == 0) return "";
// 从最长子串开始检查
for (int len = n; len >= 1; len--) {
for (int start = 0; start <= n - len; start++) {
int end = start + len - 1;
if (isPalindrome(s, start, end)) {
return s.substr(start, len);
}
}
}
return "";
}
private:
// 检查子串 s[left..right] 是否为回文
bool isPalindrome(const string& s, int left, int right) {
while (left < right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
};
时间复杂度:O(n³),遍历所有子串并检查回文的开销较高。
缺点:不适用于长字符串,但实现简单。
2.2中心扩展法
更高效的算法是中心扩展法,以每个字符为中心,向两边扩展,寻找最长的回文子串。同时,还需要考虑回文的长度可能是奇数或偶数的情况,比如“aba”是奇数长度,中心是中间的“b”;而“abba”是偶数长度,中心在两个“b”之间。因此,对于每个字符,我们需要考虑这两种情况,分别进行扩展。
比如,对于示例1中的s = "babad",从每个字符开始扩展:
以第一个字符b为中心,向左无法扩展,向右到a,此时回文是"b";以第二个字符a为中心,向左是b,向右是b,形成"bab";继续向右扩展可能得到更长的吗?这里可能还需要考虑其他中心的情况。但是,这样可能需要遍历每个字符,并且每个字符最多扩展n次,所以总的时间复杂度是O(n²),这应该比之前的O(n³)好很多。
具体步骤
1. 遍历字符串中的每一个字符,作为可能的回文中心。
2. 对每个中心,分别处理奇数长度和偶数长度的回文情况。
3. 对于每种情况,向两边扩展,直到不再满足回文条件为止。
4. 记录每次扩展得到的回文子串的起始和结束位置,比较长度,更新最长回文的信息。
比如,对于s = "cbbd",当处理第二个b的时候,以它为中心的奇数长度回文是"b",而偶数长度的情况则是和后面的b组成"bb",此时需要扩展检查两边是否相同。
核心思想
以每个字符为中心,向两边扩展寻找回文子串,并同时处理奇数和偶数长度的回文。例如,对于字符 s[i]
,分别以 s[i]
为中心(奇数长度)和以 s[i]
与 s[i+1]
为中心(偶数长度)进行扩展。
代码实现
#include <string>
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int start = 0, max_len = 0;
for (int i = 0; i < s.size(); ++i) {
int len1 = expand(s, i, i); // 奇数长度回文
int len2 = expand(s, i, i+1); // 偶数长度回文
int current_len = max(len1, len2);
if (current_len > max_len) {
max_len = current_len;
start = i - (current_len - 1) / 2; // 计算起始位置
}
}
return s.substr(start, max_len);
}
private:
int expand(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left--;
right++;
}
return right - left - 1; // 返回回文长度
}
};
时间复杂度:O(n²)
空间复杂度:O(1)