力扣题库第五题 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
题外话:一看是中等难度,以为会比较简单,天真了还是。
解题:
首先看到回文字符串,第一想到应该是回文的判断方法。
因为从中间向外判断,既能直接得出回文长度也不会做多余比较,所以这种肯定更好。
这样就想起来就容易了,那是不是从字符串开头起始,在每个字符以及字符间隙进行运算,求以他为核心时,回文长度是多少,都记录下来,然后取最大的回文长度和回文串即可。(间隙不占空间如何计算?预处理一下字符串加入特殊字符例如“#”、“&”之类的,然后该怎么算就怎么算即可。只是计算回文范围的时候注意处理就好了。)
这种思路肯定是可行的,以每个字符为中心都计算一遍,故时间复杂度为O(n^2),而空间复杂度可以做到O(1)。
优化:
到此为止了吗?回文问题是不是有更好的解法呢?上一个题计算中位数的时候,利用有序这个条件还能再进行优化,这次回文字符串更加特殊,是不是有潜在规律还没有利用呢?
事实上,还真的有,那就是著名的“拉马车算法”(Manacher’s Algorithm)。
马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性。
这种算法巧妙地利用的回文的对称属性,在判断对称中心右侧的,已知回文段内的,字符的回文长度时,可以去左侧找与他对称的已经计算过的字符。不需要再从头按部就班重新计算了。
找资料过程中发现两个帖子,讲得非常透彻,图文并茂。
链接附上,一起学习。
CSDN
知乎
附上代码:
class Solution {
public String longestPalindrome(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 boolean isPlalindrome(String s) {
int len = s.length();
for(int i = 0; i < len / 2; i++) {
if(s.charAt(i) != s.charAt(len - 1 - i)) {
return false;
}
}
return true;
}
// 预处理字符串,在两个字符之间加上#
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();
}
}