1.题目描述
给定一个字符串
s
,找到s
中最长的回文子串。你可以假设s
的最大长度为 1000。示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。示例 2:
输入: “cbbd”
输出: “bb”
2.解题思路
方法1:暴力破解方法
方法2:动态规划
新建一个dp[i][j]代表的意思是索引从i到j的子字符串是否是回文,假设s = cbbd,则可以dp对应坐标索引下的子字符串:
i = 0 1 2 3 j = 0 c cb cbb cbbd 1 b bb bbd 2 b bd 3 d 如果dp[i][j]所代表的字符串为回文,则将dp设为1.由上表可以总结出:
(1)当i = j 时,dp[j][i] = 1
(2)i-j=1时,比如dp[1][2]为bb,表示两个相邻的字符,此时我们只要判断str[1]==str[2]就能得出dp[1][2]的结果
(3)i-j>1时,我们来看dp[0][2],首先还是要判断开头和结尾是否相等,也就是判断 str[0]==str[2],假如此时str[0]=str[2],我们还要再看剩下的子串是否回文, 我们可以直接从dp[j+1][i-1]来判断剩下的子串,把结果直接拿来用,判断是否是1(1表示回文)
即有公式
dp[i][j] = (dp[i+1][j-1] &&s[i]=S[j]) == true?1:0
dp数组初始化如下:
dp[i][i] = 1 dp[i][i+1] = ( S[i] == S[i+1] ?1:0;
方法3:中心扩展方法
回文中心的两侧互为镜像。因此,回文可以从他的中心展开,并且只有2n-1个这样的中心(一个元素为中心的情况有n个,两个元素为中心的情况有n-1个)
方法4:manacher算法
(1)预处理:回文的长度可奇可偶,故在每个字符的左右都加上一个特殊字符“#”,得到长度为奇数的字符串
(2)回文子串的半径:以中间的 ‘1’ 为中心的回文子串 “#2#2#1#2#2#” 的半径是6,而未添加#号的回文子串为 “22122”,长度是5,为半径减1。
(3)子串的起始位置:(字符串前面在加一个特殊字符"$",在字符末尾加另一个特殊字符".")中间位置减去半径再除以2。
(4)子串的终点位置:起点位置+半径-1
(5)马拉车核心
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
其中p[i]代表以i为中心的半径,id为能延伸到最右端的位置的那个回文子串的中心点位置,mx是回文串能延伸到的最右端的位置
1)mx - i > p[j],即以j为半径的回文在以id为半径的回文内(j跟i是对称的),其中 j = 2*id - i,因为 j 到 id 之间到距离等于 id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i.
2)mx - i < p[j],即以j为中心的回文子串不一定完全包含于以id为中心的回文子串中,基于对称性可知,图中两个绿框所包围的部分是相同的,也就是说以i为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 p[i] = mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了,这就是后面紧跟到while循环的作用。
3)对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。
3.代码
思路1:暴力破解方法
public static String longestPalindrome(String s) {
String temp = "";
String res ="";
for(int i = 0;i<s.length();i++){
for(int j = i;j<s.length();j++){
temp = temp+s.charAt(j);
//re用来保存子字符串反转的结果
String re = new StringBuffer(temp).reverse().toString();
//子字符串跟反转的字符串相等则为回文
if(temp.equals(re)){
res = res.length()>temp.length()?res:temp;
}
}
temp = "";
}
return res;
}
方法2:动态规划思想
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if(s == null || len == 0){
return s;
}
String res = "";
int max = 0;
//创建一个行列均为字符串长度的二维数组,创建时默认初始化为false
boolean[][] dp = new boolean[len][len];
for(int j = 0; j < len; j++){
for(int i = 0; i <= j; i++){
dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1]);
if(dp[i][j]){//如果是回文字符串
//新得到的回文比之前的回文字符串要长,更新字符串长度,记录字符串
if(j - i + 1 > max){
max = j - i + 1;
res = s.substring(i, j + 1);
}
}
}
}
return res;
}
}
方法3:中心扩展方法
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, 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);
}
private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L - 1;
}
}
方法4:马拉车算法
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1)
return "";
//manacher预处理
//起点加特殊字符$
String t = "$#";
//每个字符左右都加特殊字符 #
for (int i = 0; i < s.length(); ++i) {
t += s.charAt(i);
t += "#";
}
//终点加特殊字符&
t+="&";
//马拉车算法实现
//定义半径
int []p = new int[t.length()];
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < t.length()-1; i++) {
//mancher核心算法
p[i] = mx > i ? Math.min(p[2 * id - i], mx - i) : 1;
//半径往外扩
while (t.charAt(i + p[i]) == t.charAt(i-p[i]))
++p[i];
//以i为中心的回文子串不一定完全包含于以id为中心的回文子串中
if ((mx-i) < p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
return s.substring((resCenter - resLen) / 2, (resCenter - resLen) / 2 + resLen - 1);
}
}
4.提交记录
1.动态规划
2.中心扩展
3.马拉车算法