题目:
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000s仅由数字和英文字母组成
解法一:直接暴力枚举
第一个想到就是暴力枚举。但我还是有点自己的心思的。
代码结构
代码分为两个主要部分:
-
isPalindrome函数:用于判断一个子串是否是回文。 -
longestPalindrome函数:用于遍历字符串,找到最长的回文子串。
isPalindrome 函数
bool isPalindrome(string s, int left, int right) {
while (left < right) {
if (s[left] == s[right]) {
left++;
right--;
} else {
return false;
}
}
return true;
}
功能
-
判断字符串
s中从索引left到right的子串是否是回文。
逻辑
-
使用双指针法:
-
left指针从子串的起始位置开始,向右移动。 -
right指针从子串的结束位置开始,向左移动。
-
-
比较
s[left]和s[right]:-
如果相等,则继续向中间移动指针。
-
如果不相等,则说明子串不是回文,返回
false。
-
-
如果
left和right指针相遇或交叉,说明子串是回文,返回true。
时间复杂度
-
每次调用
isPalindrome的时间复杂度是 O(n),其中n是子串的长度。
longestPalindrome 函数
string longestPalindrome(string s) {
string ans;
int length = 0;
for (int left = 0; left < s.size(); left++) {
for (int right = left; right < s.size(); right++) {
if (s[left] == s[right]) {
if (isPalindrome(s, left, right) && (right - left + 1) > length) {
length = right - left + 1;
ans = s.substr(left, right - left + 1);
}
} else {
continue;
}
}
}
return ans;
}
功能
-
遍历字符串
s,找到最长的回文子串。
逻辑
-
初始化:
-
ans:用于存储当前找到的最长回文子串。 -
length:用于记录当前找到的最长回文子串的长度。
-
-
双重循环:
-
外层循环:
left指针从字符串的起始位置开始,向右移动。 -
内层循环:
right指针从left的位置开始,向右移动。
-
-
检查子串是否是回文:
-
如果
s[left] == s[right],则检查从left到right的子串是否是回文。 -
如果是回文,并且子串的长度
(right - left + 1)大于当前记录的length,则更新length和ans。
-
-
返回结果:
-
最终返回
ans,即最长的回文子串。
-
完整代码
class Solution {
public:
//判断是不是回文串
bool isPalindrome(string s, int left, int right)
{
// int left = 0;
// int right = s.size();
while(left < right)
{
if(s[left] == s[right])
{
left++;
right--;
}else
{
return false;
}
}
return true;
}
string longestPalindrome(string s) {
string ans;
int length = 0;
for(int left = 0; left < s.size(); left++)
{
for(int right = left; right < s.size(); right++)
{
if(s[left] == s[right])
{
if(isPalindrome(s, left, right) && (right - left + 1) > length)
{
length = right - left + 1;
ans = s.substr(left, right - left + 1);
}
}else
{
continue;
}
}
}
return ans;
}
};
问题是:时间复杂度O(n^3)。虽然做了很多简化,但是还有一个用例没有通过!!!
解法二:中心拓展法
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int start = 0, maxLength = 1; // 记录最长回文子串的起始位置和长度
for (int i = 0; i < s.size(); i++) {
// 奇数长度的回文子串
int len1 = expandAroundCenter(s, i, i);
// 偶数长度的回文子串
int len2 = expandAroundCenter(s, i, i + 1);
// 取较长的回文子串
int len = max(len1, len2);
if (len > maxLength) {
maxLength = len;
start = i - (len - 1) / 2; // 计算起始位置
}
}
return s.substr(start, maxLength);
}
private:
// 中心扩展函数
int expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left--; // 向左扩展
right++; // 向右扩展
}
// 返回当前回文子串的长度
return right - left - 1;
}
};
中心扩展法的逻辑
-
核心思想:
-
回文子串的中心可能是 一个字符(奇数长度)或 两个字符(偶数长度)。
-
遍历字符串,以每个字符为中心,向左右扩展,找到最长的回文子串。
-
-
具体步骤:
-
遍历字符串中的每个字符
s[i]。 -
对于每个字符
s[i],分别以s[i]为中心(奇数长度)和以s[i]和s[i+1]为中心(偶数长度)进行扩展。 -
使用
expandAroundCenter函数向左右扩展,直到字符不匹配或超出字符串边界。 -
记录每次扩展得到的回文子串的长度,并更新最长回文子串的起始位置和长度。
-
-
时间复杂度:
-
遍历字符串需要 O(n),每次扩展需要 O(n),总时间复杂度为 O(n^2)。
-
-
空间复杂度:
-
只使用了常数级别的额外空间,空间复杂度为 O(1)。
-
解法三:动态规划
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false)); // dp[i][j] 表示 s[i..j] 是否是回文
int start = 0, maxLength = 1; // 记录最长回文子串的起始位置和长度
// 单个字符一定是回文
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
// 检查长度为 2 的子串
for (int i = 0; i < n - 1; i++) {
if (s[i] == s[i + 1]) {
dp[i][i + 1] = true;
start = i;
maxLength = 2;
}
}
// 检查长度大于 2 的子串
for (int len = 3; len <= n; len++) { // len 是子串的长度
for (int i = 0; i <= n - len; i++) { // i 是子串的起始位置
int j = i + len - 1; // j 是子串的结束位置
if (s[i] == s[j] && dp[i + 1][j - 1]) { // 状态转移
dp[i][j] = true;
if (len > maxLength) {
start = i;
maxLength = len;
}
}
}
}
return s.substr(start, maxLength);
}
};
动态规划法的逻辑
-
核心思想:
-
使用一个二维数组
dp[i][j]表示子串s[i..j]是否是回文。 -
通过状态转移方程,利用已知的小规模回文子串信息,推导出更大规模的回文子串。
-
-
状态转移方程:
-
如果
s[i] == s[j],并且dp[i+1][j-1]是回文,那么dp[i][j]也是回文。 -
即:
dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]。
-
-
初始化:
-
单个字符一定是回文:
dp[i][i] = true。 -
两个字符的子串:如果
s[i] == s[i+1],则dp[i][i+1] = true。
-
-
具体步骤:
-
遍历所有可能的子串长度
len,从 3 到n。 -
对于每个长度
len,遍历所有可能的起始位置i,计算结束位置j = i + len - 1。 -
根据状态转移方程更新
dp[i][j],并记录最长回文子串的起始位置和长度。
-
-
时间复杂度:
-
需要填充一个
n x n的二维数组,时间复杂度为 O(n^2)。
-
-
空间复杂度:
-
需要一个
n x n的二维数组,空间复杂度为 O(n^2)。
-

404

被折叠的 条评论
为什么被折叠?



