文章目录
一、题目信息
1. 题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
2. 示例1
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
3. 示例2
输入: “cbbd”
输出: “bb”
4. 题目来源
来自 LeetCode
第5题----最长回文子串
二、题目解析
1. 动态规划
1.1 思路
拿到一道题,最先想到的办法就是“暴力求解”了,简单粗暴,但是这种方法通常时间复杂度比较高,像这道题,如果是用“暴力求解”,选出所有子字符串可能的开始和结束位置,并检验它是不是回文,然后选出最长的回文串。这样做的时间复杂度是O(n)
。解法没有问题,但是有可能 timeout limit
。
我们考虑动态规划来优化“暴力法”。
string longestPalindrome(string s)
假设 s
长度为 n
,则开辟一个 n2 大小的二维数组
dp[n][n]
,二维数组可以是动态开辟,但是这道题说字符串的长度不会超过1000
,我们直接开辟了 dp[1001][1001]
,dp[j][i]
表示s[j]
到s[i]
所表示的子串是否为回文串,是则为1,否则为0。
会有两种情况:
(1)s[j] == s[i]
,只要s[j+1]
到s[i-1]
是回文串,s[j]
到s[i]
就是回文串,如果s[j+1]
到s[i-1]
不是回文串,则s[j]
到s[i]
也不是回文串;
(2)s[j] != s[i]
,那么s[j]
到s[i]
一定不是回文串。
1.2 状态转移方程
d p [ j ] [ i ] = { d p [ j + 1 ] [ i − 1 ] , s[j] == s[i] 0 , s[j] != s[i] dp[j][i] = \begin{cases} dp[j+1][i-1], & \text{s[j] == s[i]} \\ 0, & \text{s[j] != s[i]} \end{cases} dp[j][i]={dp[j+1][i−1],0,s[j] == s[i]s[j] != s[i]
1.3 复杂度分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
1.4 代码实现
class Solution {
public:
string longestPalindrome(string s) {
// 我们考虑动态规划
// dp[s.size()][s.size()]
// dp[i][j] == true 就是 si...sj是回文子串
// 状态转移方程: p(i,j) = (p(i+1, j-1) && si == sj)
int n = static_cast<int>(s.size());
if(n < 1)
{
return "";
}
// 记录最长回文串长度
int max_len = 0;
// 记录回文串开始的下标
int start = 0;
// 状态数组
int dp[1001][1001] = {0};
for(int i = 0; i < n; ++i)
{
for(int j = 0; j <= i; ++j)
{
// 如果 i,j 相邻或相等,直接判断s[i]与s[j]是否相等
if(i-j < 2)
{
dp[j][i] = (s[j] == s[i]);
}
else
{
dp[j][i] = (dp[j+1][i-1] && (s[i] == s[j]));
}
if(dp[j][i] && max_len < i-j+1)
{
max_len = i-j+1;
start = j;
}
}
}
return s.substr(start, max_len);
}
};
2. 中心扩展算法
2.1 思路
如果一段字符串是回文串,那么以某个字符为中心的前缀和后缀必定是相同的。例如,以回文串"aba"
为例,以b
为中心,它的前缀和后缀都是a
。因此,我们可以枚举中心位置,然后再在该位置上扩展,记录并更新得到的最长回文串长度。
2.2 注意事项
需要分别考虑字符串长度为奇数和偶数的情况。
2.3 复杂度分析
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
2.4 代码实现
class Solution {
public:
string longestPalindrome(string s) {
// 中心扩展法
// 如果一个字符串是回文串,那么以某个字符为中心的前缀和后缀必然相同
// 例如回文串 "aba" 以 b 为中心,前后缀都是 a
int n = static_cast<int>(s.size());
if(n < 1)
{
return "";
}
int max_len = 0;
int start = 0;
int tmp = 0;
// i 为回文中心位置
for(int i = 0; i < n; ++i)
{
// 回文长度为奇数
for(int j = 0; ((i-j >= 0) && (i+j < n)); ++j)
{
if(s[i-j] != s[i+j])
break;
tmp = j * 2 + 1;
}
if(tmp > max_len)
{
max_len = tmp;
start = i - max_len/2;
}
// 回文长度为偶数
for(int j = 0; ((i-j >= 0) && (i+j+1 < n)); ++j)
{
if(s[i-j] != s[i+j+1])
{
break;
}
tmp = j * 2 + 2;
}
if(tmp > max_len)
{
max_len = tmp;
start = i - max_len/2 + 1;
}
}
return s.substr(start, max_len);
}
};
3. Manacher算法
// TODO