给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
/*
* @lc app=leetcode.cn id=5 lang=java
*
* [5] 最长回文子串
*
* https://leetcode-cn.com/problems/longest-palindromic-substring/description/
*
* algorithms
* Medium (36.34%)
* Likes: 4959
* Dislikes: 0
* Total Accepted: 953.6K
* Total Submissions: 2.6M
* Testcase Example: '"babad"'
*
* 给你一个字符串 s,找到 s 中最长的回文子串。
*
*
*
* 示例 1:
*
*
* 输入:s = "babad"
* 输出:"bab"
* 解释:"aba" 同样是符合题意的答案。
*
*
* 示例 2:
*
*
* 输入:s = "cbbd"
* 输出:"bb"
*
*
*
*
* 提示:
*
*
* 1 <= s.length <= 1000
* s 仅由数字和英文字母组成
*
*
*/
1. 暴力解法
class Solution {
// 暴力求解
public String longestPalindrome(String s) {
int len=s.length();
if(len < 2){
return s;
}
// s.charAt(i) 每次都会检查数组下标是否越界,因此先转换成字符数组,这一步非必需
char[] charArray = s.toCharArray();
int maxLen = 1; // 如果没有长度超过1的最长回文子串,则最长回文子串长度为1
int begin = 0;
//枚举所有长度严格大于 1 的字串 charArray[i..j]
for(int i=0;i<len-1;i++){
for(int j=i+1;j<len;j++){
// 遍历的子串长度 需要 超过当前最长的回文子串长度,减少遍历数量
if(j-i+1 > maxLen && validPalindromic(charArray, i, j)){
maxLen = j-i+1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen); // exclusive endIndex
}
/**
* 验证子串 s[left...right]是否为回文串
*/
private boolean validPalindromic(char[] charArray,int left,int right){
while(left < right){
if(charArray[left]!=charArray[right]){
return false;
}
left++;
right--;
}
return true;
}
}
2. 中心扩散法:
class Sulution{
// 中心扩散
/**
* 中心扩散法:
* 枚举所有可能的中心回文子串的中心位置
* 中心位置可能是一个字符,也可能是两个相邻的字符
* 记录最长回文子串的相关变量
*/
public String longestPalindrome(String s) {
int len = s.length();
// 特殊判断
if(len < 2){
return s;
}
// 最长回文子串相关变量
int maxLen=1;
int begin=0;
char[] charArray = s.toCharArray();
// 由于最后位置的字符不能向右进行扩展了,
//所以遍历枚举的时候 不需要枚举该位置,只需要枚举到len-2这个位置
for(int i=0; i< len-1;i++){
int oddLen = expandAroundCenter(charArray, i, i);
int evenLen = expandAroundCenter(charArray, i, i+1);
int curLen = Math.max(oddLen,evenLen);
if(curLen > maxLen){
maxLen=curLen;
begin=i-(maxLen-1)/2;
}
}
return s.substring(begin, begin+maxLen);
}
/**
*
* @param charArray 原始字符串的字符数据
* @param left 起始左边界(可以取到)
* @param right 起始右边界(可以取到)
* @return
*/
private int expandAroundCenter(char[] charArray,int left,int right){
int len = charArray.length;
int i = left;
int j = right;
while(i >= 0 && j < len){
if(charArray[i]==charArray[j]){
i--;
j++;
}else{
break;
}
}
// 跳出while循环时,恰好满足s.charAt(i)!=s.charAt(j)
// 回文子串长度为 j - i + 1 - 2 = j - i - 1
return j-i-1;
}
}
3. 动态规划法:
基础夯实:java中 布尔类型的变量初始值默认为false。
动态规划,最重要的是得到状态转移方程,通过之前计算得到的结果装填,来计算得到当前需要求解的状态,而不是重新求解。
class Solution {
/**
* 动态规划:
* 回文串天然具有状态转移性质的是,这是因为一个回文去掉两头以后,剩下的部分依然是回文
*
* 状态: dp[i][j] 表示子串 s[i...j]是否为回文子串 ('[]') 表示左右闭区间
* 得到 子串的转移状态方程 dp[i][j] = (s[i]==s[j]) and dp[i+1][j-1] (dp[i+1][j-1] 表示由于判断了首尾字符相等,所以去掉了头尾继续判断下一个字符)
* 由于使用了下标进行访问,就一定要考虑到下标是否有效,就有了边界条件
* j - 1 - (i+1) + 1 < 2 表示 下标j-1 字符和 下标i+1字符 之间不构成区间,
* 此时 下标j-1 字符和 下标i+1字符 之间的字符数为1或0,所以不构成字符串区间,
* 也就是这个范围的长度 严格小于2的时候,dp[i+1][j-1]没有意义,
* 整理得 j - i < 3 => j - i + 1 < 4
* 这个表达式的语义就是 当s[i..j]的长度为2或者3时,就不用检查子串(s[i+1...j-1])是否回文了,就不需要进行状态转移了。
*
* 动态规划实际上是在填写一张二维表格
* 单个字符一定是回文串,所以首先可以把主对角线上的值都赋值为true
* 初始化: dp[i][i]=true (表示对角线,这部分可以不进行)
* 输出: 在得到一个状态的值为true的时候,记录起始位置和长度,填表完成以后再截取 (填表为:填充dp矩阵)
*
* i <= j
* 状态转移方程: dp[i][j] = (s[i]==s[j]) and (j - i < 3 or dp[i+1][j-1])
*
* 由于 dp[i][j] 参考它左下方的值 dp[i+1][j-1]:
* (1) 先升序填列;
* (2) 再升序填行;
*
* */
public String longestPalindrome(String s){
int len = s.length();
if(len < 2){
return s;
}
int maxLen=1;
int begin=0;
// dp[i][j] 表示 s[i...j]是否是回文串,
// 重点 dp[i][j] 不是代表 charArray[i]==charArray[j]
boolean[][] dp = new boolean[len][len];
for(int i = 0; i< len;i++){
dp[i][i]=true;
}
char[] charArray = s.toCharArray();
// 注意:左下角先填,下面是先按照列j填,然后按照行i填写
for(int j = 1; j < len; j++){
for(int i = 0;i<j;i++){
if(charArray[i]!=charArray[j]){
dp[i][j]=false;
}else{
if(j-i<3){ // 如果其子串[i+1...j-1]含有0或1个元素的时候,则不需要状态转移
dp[i][j]=true;
}else { // 此时需要进行状态转移,转移到左下角 下一行上一列
dp[i][j] = dp[i+1][j-1]; // 利用之前计算得到的结果
}
}
// 只要 dp[i][j]==true成立,就表示子串 s[i...j]是回文
// 此时记录回文长度和回文起始位置
if(dp[i][j] && j-i+1 > maxLen){
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
4. Manacher算法
最后是这个问题的终极解法,Manacher算法。
专门用于查找最长回文子串的算法,时间复杂度为O(n)。
面试不要求,有时间时学习下。
力扣链接。