题目描述:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
我估么着看到马拉车算法部分人会和我一样一脸懵逼,听都没听过,那我先粗略说下马拉车。
分步来解决问题:
①奇偶个数
回文字符串比较恶心的就是奇数偶数了,那么如果在每个字符前后之间插入一个一样特殊字符,那么以特殊字符为中心的回文就是偶数个,否则就是奇数个。
举例:“babaad”
我们插入“#”,就变成了“#b [#a#b #a#]a# d#”。
看着有点乱,高亮部分是偶数个,括号部分是奇数个,这样一来我们就无须在意奇偶个数问题了,因为全变成了奇数个。
②边界问题
这样我们还是需要判断边界情况,不如再加上边界。
举例:“#b#a#b#a#a#d#”
加上前后边界,变成“$#b#a#b#a#a#d#*”。
因为 $ 和 *都不同于任何一个字符;假设判断第一个“#”字符,他的前后字符“ $ ”和“ b ”不同会直接退出循环判断。
③简化思想(马拉车核心)
试想一下,现在有个字符串“1232145412321”,整个字符串关于5对称,第一个3在5的对称范围内有一个“12321”的小对称,那么对应的5的右边的3必然也有“12321”的小对称。
这样不用每次都从零开始,循环判断其左右是否相同,可以直接获取对称位置[5左边的3]的对称半径。需要注意的不能超过对称范围[5的半径是7]。
举例:“$#a#b#a#b#a#b#*”
创建一个记录对称半径的数组p,一个记录最远对称范围的值range,一个记录拥有最远对称的index值idx。
- 从第一个#开始,while(i-p[i] == i+p[i])循环可知p[1] = 1,只有自己一个,此时idx更新为1,range更新为1;
- 到第二个字符“a”,因为超出了idx的对称范围 (0,2),所以while循环得p[2] = 2,idx = 2,range= 2;因为2+2 = 4,比1+1=2更远。
- 第三个字符“#”,因为在idx的对称范围内(0,4),所以p[3] = min(p[2*idx-i],idx+range-i);之所以取最小,是担心前边出现的那个字符对称半径超出了idx的对称范围。不懂的话下边会说到。
- 依次类推,八位置的字符“b”的p[8] = 6,它的对称范围是(2,14),即“#b#a#b#a#b#”。
那么我们现在到了12位置的“b”,它相对于这个8位置的“b”的对称位置为4位置的“b”,我们可以知道p[4] = 4,即“#a#b#a#”,但是p[12]肯定不是4,越界了。
这是因为8位置的对称范围是(2,14),而4位置是(0,8),0小于8,超出了对称范围,所以12位置取min(p[2*idx-i],idx+range-i);
大致就是这么个流程了,简单叙说一番。
反思错误
①还是在判断边界的时候会出错,p数组存的对称半径是包括自己的!
②StringBuilder没有记住,写成了StringBuild,还用错了方法,应该是append,我写的add。
Java代码
/**
* len 字符串的长度
* cslen 新字符数组长度
* cs 新字符数组
* idx 最远范围的index值
* range idx的对称半径
* maxi 最长回文的index值
* maxm maxi的对称半径
* p 记录对称半径
*/
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
int cslen = 2*len+3;
//初始化
char[] cs = new char[cslen];
cs[0] = '$';
cs[1] = '#';
cs[cslen-1] = '.';
for(int i=2,j=0;i<cslen-1;++i,++j){
cs[i] = s.charAt(j);
cs[++i] = '#';
}
// for(int i=0;i<cslen;++i)
// System.out.print(cs[i]);
//马拉车
int idx = 0;
int range = 0;
int maxi = 0;
int maxm = 0;
int[] p = new int[cslen];
for(int i=1;i<cslen-1;++i){
//i在idx对称范围内
if(i<idx+range)
p[i] = Math.min(p[2*idx-i],idx+range-i);
// System.out.println(i+" "+idx+" "+range+" "+p[i]);
//循环判断左右是否对称
while(cs[i-p[i]] == cs[i+p[i]]){
++p[i];
}
//记录最长的回文串index和半径
if(maxm<p[i]){
maxi = i;
maxm = p[i];
}
//当出现更远的位置时,更新
if(i+p[i]>idx+range){
idx = i;
range = p[i];
}
}
StringBuilder sb = new StringBuilder();
for(int i=maxi-maxm+1;i<maxi+maxm;++i){
if(cs[i] != '#')
sb.append(cs[i]);
}
return sb.toString();
}
}
提交结果
执行结果:通过
显示详情
执行用时 :5 ms, 在所有 java 提交中击败了97.68% 的用户
内存消耗 :36.1 MB, 在所有 java 提交中击败了91.28% 的用户