题目:
给你一个字符串 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)。
-