给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
方法一:中心扩展算法
中心扩展就是把给定的字符串的每一个字母或两个字母之间空隙当做中心,向两边扩展,这样来找
长度为奇数的回文串,比如a, aba, abcba,以字母为中心
长度为偶数的回文串,比如aa, abba,以两个字母之间空隙为中心
public String longestPalindrome(String s) {
if(null == s || s.length() <= 1){
return s;
}
// 记录回文子串的开始位置
int start =0;
// 记录回文子串的结束位置
int end = 0;
for(int i=0; i end -start){
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start,end+1);
}
/**
* 找到以left和right为中心的最大回文串长度
* @param s
* @param left
* @param right
* @return
*/
private int expandAroundCenter(String s, int left, int right) {
while(left >=0 && right
复杂度分析:
方法二:动态规划
在动态规划的思想中,总是希望把问题划分成相关联的子问题;然后从最基本的子问题出发来推导较大的子问题,直到所有的子问题都解决。
假设字符串s的长度为length,建立一个length*length的矩阵dp。令 dp[i][j] 表示 S[i] 至 S[j] 所表示的子串是否是回文子串。
由此可以写出状态转移方程:
d p [ i ] [ j ] = { t r u e , i = = j S [ i ] = = S [ j ] , j − i < 3 S [ i ] = = S [ j ]    & &    d p [ i + 1 ] [ j − 1 ] , j − i > = 3 dp[i][j]=\begin{cases} true,&i == j\\ S[i]==S[j],&j - i < 3\\ S[i]==S[j]\;\&\&\;dp[i+1][j−1] ,&j-i>=3\\ \end{cases} dp[i][j]=⎩⎪⎨⎪⎧true,S[i]==S[j],S[i]==S[j]&&dp[i+1][j−1],i==jj−i<3j−i>=3
需要注意的点是,因为要访问dp[i+1][j-1],因此 i 是从大到小的,j是从小到大的。
public String longestPalindrome2(String s) {
if(null == s || s.length() <= 1){
return s;
}
int len = s.length();
boolean[][] dp = new boolean[len][len];
int left = 0;
int right = 0;
for (int i = len - 1 ; i >= 0; i--) {
// 将对角线(即i==j的情况)赋值为true
dp[i][i] = true;
for (int j = i+1; j < len; j++) {
dp[i][j] = s.charAt(i) == s.charAt(j) &&( j-i<3||dp[i+1][j-1]);
if(dp[i][j] && right-left
复杂度分析:
方法三:Manacher(马拉车)算法
Manacher算法,又叫“马拉车”算法,可以在时间复杂度为O(n)的情况下求解一个字符串的最长回文子串长度的问题。
/**
* 马拉车算法
* @param s
* @return
*/
public String longestPalindrome3(String s) {
// 先预处理字符串
String str = preHandleString(s);
// 处理后的字串长度
int len = str.length();
// 右边界
int rightSide = 0;
// 右边界对应的回文串中心
int rightSideCenter = 0;
// 保存以每个字符为中心的回文长度一半(向下取整)
int[] halfLenArr = new int[len];
// 记录回文中心
int center = 0;
// 记录最长回文长度
int longestHalf = 0;
for(int i = 0; i < len; i++) {
// 是否需要中心扩展
boolean needCalc = true;
// 如果在右边界的覆盖之内
if(rightSide > i) {
// 计算相对rightSideCenter的对称位置
int leftCenter = 2 * rightSideCenter - i;
// 根据回文性质得到的结论
halfLenArr[i] = halfLenArr[leftCenter];
// 如果超过了右边界,进行调整
if(i + halfLenArr[i] > rightSide) {
halfLenArr[i] = rightSide - i;
}
// 如果根据已知条件计算得出的最长回文小于右边界,则不需要扩展了
if(i + halfLenArr[leftCenter] < rightSide) {
// 直接推出结论
needCalc = false;
}
}
// 中心扩展
if(needCalc) {
while(i - 1 - halfLenArr[i] >= 0 && i + 1 + halfLenArr[i] < len) {
if(str.charAt(i + 1 + halfLenArr[i]) == str.charAt(i - 1 - halfLenArr[i])) {
halfLenArr[i]++;
} else {
break;
}
}
// 更新右边界及中心
rightSide = i + halfLenArr[i];
rightSideCenter = i;
// 记录最长回文串
if(halfLenArr[i] > longestHalf) {
center = i;
longestHalf = halfLenArr[i];
}
}
}
// 去掉之前添加的#
StringBuffer sb = new StringBuffer();
for(int i = center - longestHalf + 1; i <= center + longestHalf; i += 2) {
sb.append(str.charAt(i));
}
return sb.toString();
}
// 预处理字符串,在两个字符之间加上#
private String preHandleString(String s) {
StringBuffer sb = new StringBuffer();
int len = s.length();
sb.append('#');
for(int i = 0; i < len; i++) {
sb.append(s.charAt(i));
sb.append('#');
}
return sb.toString();
}
复杂度分析:
马拉车算法理解起来有点难,这里给出几个比较好的链接,有兴趣的可以学习下:
Manacher 算法解析
如何找到字符串中的最长回文子串?
题目地址:https://leetcode-cn.com/problems/longest-palindromic-substring/