题目:
给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
解法一:直接暴力枚举
第一个想到就是暴力枚举。但我还是有点自己的心思的。
代码结构
代码分为两个主要部分:
-
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)。
-