5. 最长回文子串
难度 中等
给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
题解
这里需要解释两个概念,回文和子串。回文就是从左往右和从右往左读都是一样的,比如abcba。子串和子序列不一样,子串必须连续,子序列不一定连续。
这道题是让我们找到最长的回文子串,当然暴力的方法肯定能找到,就是遍历+枚举,但是这应该会超时,就找别的方法去了,三年前看学过最长回文子串的算法,使用了动态规划,中心扩展法和马拉车法。
动态规划
这道题怎么动态规划呢,如果s[i]和s[j]相同,且s[i + 1]到s[j - 1]是回文子串,那么s[i]到s[j]就是回文子串。那么我们动态规划方程就为dp[i] [j] = dp[i + 1] [j - 1] + 2。
实现上面的动态规划,就需要一个辅助数组dp,但是我们并不是用上面的记录长度数组,而是换为布尔数组,如果dp[i] [j]为true的话,那么从i到j的子串是回文子串。
动态规划四部曲:
- 确定状态
- 第一种,s[i] = s[j]
- 如果dp[i + 1] [j - 1]为true,那么dp[i] [j] = true
- 如果dp[i + 1] [j - 1]为false,那么dp[i] [j] = false
- 第二种,s[i] != s[j],那么dp[i] [j] = false
- 第一种,s[i] = s[j]
-
初始条件和边界情况
- 如果长度为1,那肯定是回文子串,dp[i] [i] = true
- 如果长度为2,如果s[i]和s[i + 1]相等的话,dp[i] [i + 1] = true
-
状态转移方程
- 长度大于3,如果dp[i + 1] [j - 1]为true,且s[i] = s[j],那么dp[i] [j] = true
-
确定计算顺序
- 枚举从长度3开始的子串
class Solution {
public String longestPalindrome(String s) {
int len = s.length();//数组长度
int start = 0;//最长回文子串开始下标
int maxlength = 1;//一定能找到长度为1的子串
boolean p[][] = new boolean[s.length()][s.length()];//辅助数组
for(int i = 0 ; i < len; i++){//初始化
p[i][i] = true;//长度为1的子串,一定是回文子串
if(i < len - 1 && s.charAt(i) == s.charAt(i + 1)){//长度为2的子串,判断成功,更新状态
p[i][i + 1] = true;
start = i;
maxlength = 2;
}
}
for(int strlen = 3; strlen <= len; strlen++){//长度为3以上的数组
for(int i = 0; i <= len - strlen; i++){//从下标为0开始遍历
int j = i + strlen - 1;//长度strlen = j - i + 1,移项
if(p[i + 1][j - 1] && s.charAt(i) == s.charAt(j)){//如果dp[i+1][j-1]为true,且s[i]=s[j],是回文子串,更新状态
p[i][j] = true;
maxlength = strlen;
start = i;
}
}
}
return s.substring(start, start + maxlength);
}
}
中心扩展法
中心扩展法就是从当前位置开始扩展,为什么从这个位置开始扩展呢,我觉得是因为中心扩展的话,只需要判断最新扩展的字符,而不像暴力法从一遍开始枚举,需要重复判断整个子串,这样时间效率上应该比较高。
不过中心扩展法有点需要注意,就是回文子串长度是奇数还是偶数。
- 奇数,就是中心点对称,开始坐标是一致的
- 偶数,就是中心对称,开始坐标不一致
class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length() < 1){//长度为0
return "";
}
int start = 0;//记录最长回文子串的开始坐标
int end = 0;//记录最长回文子串的结束坐标
for(int i = 0; i < s.length(); i++){//从左到右枚举
int len1 = expandAroundCenter(s, i, i);//最长回文子串是奇数
int len2 = expandAroundCenter(s, i, i + 1);//最长回文子串是偶数
int len = Math.max(len1, len2);//获取最长长度
if(len > end - start){//如果当前最长回文子串长度大于之前的最长回文子串,更新状态
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);//返回从start到end+1的子串
}
public int expandAroundCenter(String s, int left, int right){
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){//边界限制,扩展字符判断
--left;
++right;
}
return right - left - 1;
}
}