5. 最长回文子串(较难)

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring

思路1:

暴力求解,这里会超时,时间复杂度o(n^3)

说明:暴力解法时间复杂度高,但是思路清晰、编写简单。由于编写正确性的可能性很大,可以使用暴力匹配算法检验我们编写的其它算法是否正确。优化的解法在很多时候,是基于“暴力解法”,以空间换时间得到的,因此思考清楚暴力解法,分析其缺点,很多时候能为我们打开思路。

参考代码 1:Java 代码正常运行,C++ 代码超出内存限制,Python 代码超时。

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

思路2

方法二:中心扩展算法
https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/

思路3

动态规划

“动态规划”最关键的步骤是想清楚“状态如何转移”,事实上,“回文”是天然具有“状态转移”性质的:

一个回文去掉两头以后,剩下的部分依然是回文(这里暂不讨论边界)。

依然从回文串的定义展开讨论:

1、如果一个字符串的头尾两个字符都不相等,那么这个字符串一定不是回文串;

2、如果一个字符串的头尾两个字符相等,才有必要继续判断下去。

(1)如果里面的子串是回文,整体就是回文串;

(2)如果里面的子串不是回文串,整体就不是回文串。

即在头尾字符相等的情况下,里面子串的回文性质据定了整个子串的回文性质,这就是状态转移。因此可以把“状态”定义为原字符串的一个子串是否为回文子串。

状态转移方程如下
$$
P(i, j)={ \text { true } \text { s[i,j] 是回文串 } \ \text { false } \mathrm{s}[\mathrm{i}, \mathrm{j}] \text { 不是回文串 }
$$

$$
P(i, j)=(P(i+1, j-1) & & S[i]==S[j])
$$

本思路代码见代码3

本思路中时间复杂度为o(n^2),空间复杂度为o(n^2),,其中空间可以优化为o(n)—–未写

代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//这代码看个眼熟就行,别细看了,就是暴力求解
class Solution {
public String longestPalindrome(String s) {
return subString(s);
}
public static String subString(String s){
//判断子串
int max = 0;
String maxstring = "";
for(int i=0;i<s.length();i++){
//
for(int j=i+1;j<=s.length();j++){
String s1 = s.substring(i,j);
if(s1.length()>max && huiwen(s1)){
max = Math.max(max,s1.length());
maxstring = s1;
}
}
}
return maxstring;
}
public static boolean huiwen(String s){
//判断回文
int len = s.length();
for(int i=0;i<len/2;i++){
if(s.charAt(i)!=s.charAt(s.length()-i-1)){
return false;//回文串长度
}
}
return true;
}
}

代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public String longestPalindrome(String s) {
if(s.length()<2){
return s;
}
int maxlen = 0;
String maxstr = "";
// 中心位置枚举到 len - 2 即可
for(int i=0;i<s.length()-1;i++){
String str1 = Judge(s,i,i);
String str2 = Judge(s,i,i+1);
String maxstr1 = str1.length()>str2.length()?str1:str2;
if(maxstr1.length()>maxlen){
maxstr = maxstr1;
maxlen = maxstr1.length();
}
}
return maxstr;

}
//判断是不是回文
// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
String Judge(String s,int left,int right){
while(left>=0 && right<s.length()){
if(s.charAt(left)==s.charAt(right)){
left--;
right++;
}
//这个剪枝很必要,要不然会超时
else {
break;
}
}
// 这里要小心,跳出 while 循环时,恰好满足 s.charAt(i) != s.charAt(j),因此不能取 i,不能取 j
return s.substring(left+1,right);
}
}

代码3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
String res ="";
//dp[i][j]表示,在s中的最长回文子串长度
boolean[][] dp = new boolean[len][len];
for(int i=len-1;i>=0;i--){
for(int j=i;j<len;j++){
//如果相等
if(s.charAt(i)==s.charAt(j)){
dp[i][j]=j-i<2?true:dp[i+1][j-1];
}else{
dp[i][j]=false;
}
//判断此长度是否大于之前的res
if(j-i+1>res.length() && dp[i][j]){
res = s.substring(i,j+1);
}
}
}
return res;
}
}