题目:5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
示例 3:
输入:s = “a”
输出:“a”
示例 4:
输入:s = “ac”
输出:“a”
方法一:动态规划
// An highlighted block
// 1、动态规划
// str[i,j] 是回文 <==>str[i+1.j-1]是回文 && str[i]==str[j]
// len = 1是回文,当两个字母相同 len = 2 是回文
class Solution {
public String longestPalindrome(String s) {
char[] c= s.toCharArray();
int len = s.length();
if (len < 2) return s;
boolean[][] dp = new boolean[len][len];
for(int i = 0; i < len; i++){
dp[i][i] = true;
}
int j = 0;
int maxLength = 1;
int begin = 0;
for(int L = 2; L <= len; L++){ //字符串长度
for(int i = 0; i <= len - L; i++){ //字符串左边界
j = i + L - 1; //字符串右边界
if(c[i]!=c[j]){
dp[i][j]=false;
}
else if(j-i ==1){ //长度为2,如果相等就是回文
dp[i][j] =true;
}
else{
dp[i][j] = dp[i+1][j-1];
}
if(L>maxLength && dp[i][j] ==true){
maxLength = L;
begin = i;
}
}
}
return s.substring(begin, begin + maxLength); // 不包括尾下标
}
}
方法二:中心扩展
一、奇偶分开来考虑
// 2、中心扩展
class Solution {
public String longestPalindrome(String s) {
if(s.length()<2) return s;
int begin = 0;
int end = 0;
for(int i = 0; i < s.length()-1; i++){
int len1 = expandAroundCenter(s,i,i); //奇数
int len2 = expandAroundCenter(s,i,i+1); //偶数
int maxlen = Math.max(len1,len2);
if(maxlen > end-begin+1){
begin = i-(maxlen-1)/2;
end = i + maxlen/2;
}
}
return s.substring(begin, 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; // 注意最后一次循环多扩展了一次,所以要right-left+1-2
}
}
二、奇偶统一来考虑
图来自力扣解题
长度为 n 的字符串会生成2n−1 组回文中心 [li, ri],其中 li=i/2,ri=li+(i mod 2)。这样我们只要从 0 到 2n−2 遍历 i,就可以得到所有可能的回文中心。
其中找回文子串的代码如下:
class Solution {
public int countSubstrings(String s) {
int n = s.length();
int ans = 0;
for (int i = 0; i < 2*n+1; ++i) { //统一奇偶
int l = i/2; //左边界
int r = i / 2 + i % 2; //右边界
while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {
--l;
++r;
++ans;
}
}
return ans;
}
}
方法三:马拉车
// 3、马拉车
class Solution {
public String longestPalindrome(String s) {
// 重新构造str 因为考虑回文的奇偶性,每个字符中间插入# 前后插入分别^#和#$ ,首尾加入特殊字符为了退出循环。
StringBuffer str1 = new StringBuffer('#');
if (s.length() == 0) {
return "^$";
}
str1.append("^#");
for(int i = 0; i < s.length();i++){
str1.append(s.charAt(i));
str1.append('#');
}
str1.append('$');
String str = str1.toString();
int C = 0;
int R = 0;
int len = str.length();
int[] P = new int[len];
P[0] = 0;
for(int i = 1; i < len-1; i++){ // 去掉前后^和$
int i_mirror = 2 * C - i;
if(i < R){
P[i] = Math.min(R - i, P[i_mirror]); // 防止超出R
}else{
P[i] = 0; //在边界
}
while(str.charAt(i-1-P[i])==str.charAt(i+1+P[i])){ //更新以i为中心的回文臂长
P[i]++;
}
if(i + P[i] > R){ //更新R
C = i;
R = i + P[i];
}
}
int begin = 0;
int maxlen = 0;
for(int i = 1; i < len - 1; i++){ // 去掉前后^和$
if(P[i] > maxlen){
maxlen = P[i];
begin = (i - P[i])/2;
}
}
return s.substring(begin, begin + maxlen);
}
}