相应leetcode题目
leetcode 第5题. 最长回文子串

1.回文串
即从两边开始到中间,对应的字符都相同
public boolean isPalindrome(String str){
int i=0,j=str.length();
while (i<j){
if(str.charAt(i++)!=str.charAt(j++)){
return false;
}
}
return true;
}
从leetcode 第5题的示例输出: “bab” 和 “bb” , 回文串的长度有奇偶之别 ,长度为偶数的回文串不能从其本身的字符来确定中心对称点 ,因此可以添加辅助字符来帮助确定中心对称的字符: ( 添加的辅助字符必须是回文串中未曾出现过的字符)
假设对于只用26个小写字母构成的字符串而言 ,则可以用一些标点符号来充当辅助字符 ,比如 ‘#’ 。 那么对于题中示例长度为偶数的回文串 ,添加辅助字符后可以形成 " #b#b# " ,该字符串则以 ‘#’ 成中心对称 。
对与长度为奇数的回文串 ,尝试添加同样的辅助字符 ,同样也不影响其对称的特点 ,如 " #b#a#b# "。 因此可以不管字符串的长度 ,都添加辅助字符来帮助确定回文串的中心 。
2.寻找字符串中的回文子串
上面通过添加辅助字符可以确定回文串的对称中心 ,同样 ,对于寻找字符串中的回文子串 ,也可以添加辅助字符来帮助寻找 。
既然回文串关于中心对称 ,那可以用中心扩散法( 以某一个字符为中心 ,从中间向两边 ,匹配两边的每一个字符 )在字符串中找回文子串 ,即遍历字符串中的每个字符 ,在遍历的过程中 ,以每个字符为中心向两边扩散 ,寻找回文子串 。
如 ,在 " cabafabae " 中寻找回文子串 ,先添加辅助字符形成 "#c#a#b#a#f#a#b#a#e " ,然后遍历该字符串的每个字符 ,并在遍历的过程中 ,以每个字符为中心向两边扩散 。
在扩散的过程中 ,比如在遍历字符串 " #c#a#b#a#f#a#b#a#e " 到字符 ‘f’ 的时候 ,已经可以确定的回文子串有 ” #a#b#a#f#a#b#a# “ 和 ” #a#b#a# “ 等等,那么在接下来继续遍历右边字符 ’b‘ 的时候 ,可以根据回文串对称的特点,确定字符 ’f‘ 右边的也有一个关于以 ’b‘ 字符为中心的回文子串 ” #a#b#a# “ 。
3.marcher算法
根据上面的示例 ,对中心扩散法进行优化 ,即并不需要在每个字符的遍历上都用中心扩散 ,可以根据回文串中心对称的特点 ,在向右遍历的同时 ,可以利用对称的特点 ,直接利用左边已经遍历过的结果。
( 用一个数组 record 记录 以该字符为中心的回文子串的长度 ,由于左右对称 长度 只记录一边的长度 )
不过 ,在利用左边的结果的同时 ,有一些边界问题需要注意 :
1. 已确定回文子串的边界覆盖问题 ,如下 :
在上述字符串 " #c#a#b#a#f#a#b#a#e " 中寻找回文子串的过程中 ,在遍历到字符 ’f‘ 的时候 ,可以确定的回文子串有 ” #a#b#a#f#a#b#a# “ 和 ” #a#b#a# “ 等等 ,以 ’f‘ 为中心的回文子串 ” #a#b#a#f#a#b#a# “ 能覆盖到的位置为 16 ,

在遍历到 ’f’ 右边的字符 ‘b’ 的时候 ,通过左右对称可知,右边以 ’b‘ 字符为中心的回文子串的长度为 3 ,与 以 ’f‘ 字符为中心的回文子串的 边界重合 了 。假如位置 17 的字符为 ’f‘ ,那么以 ’b‘ 字符为中心的回文子串为 ” #f#a#b#a#f# “ ,长度为 5 。这样照抄 ‘f’ 左边 ‘b’ 记录的回文子串的长度就是错误的了 。
因此在边界重合的时候 ,需要继续对该字符进行中心扩散。
2. 右边回文子串 超过字符串长度 问题:
比如 ,

在红色圈中的字符 ‘c’ 时 ,以它为中心的回文子串边界和 以前一个字符 ‘#’ 为中心的回文子串边界 ,根据上一个注意的问题 ,字符 ’c‘ 需要继续对该字符进行中心扩散 ,并以其为新的中心点 。
但在遍历下一个 字符 ’#‘ 的时候 ,利用 字符 ’c‘ 左边的记录 4 ,那么以 这个 字符 ‘#’ 为中心的回文子串边界 就会到10 ,超过了字符串的长度 ,需要根据字符串的长度进行改正 ,改为 2 。

弄清楚上面的一些注意问题的时候 ,就可以实现 marcher 算法了
// 预处理字符串,在两个字符之间加上#
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();
}
// 寻找最长回文字串
public String findLongestPlalindromeString(String s) {
// 先预处理字符串
String str = preHandleString(s);
// 处理后的字串长度
int len = str.length();
// 右边界
int rightSide = 0;
// 右边界对应的回文串中心
int rightSideCenter = 0;
// 保存以每个字符为中心的回文长度一半(向下取整)
int[] record = 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;
// 根据回文性质得到的结论
record[i] = record[leftCenter];
// 如果超过了右边界,进行调整
if(i + record[i] > rightSide) {
record[i] = rightSide - i;
}
// 如果根据已知条件计算得出的最长回文小于右边界,则不需要扩展了
if(i + record[leftCenter] < rightSide) {
// 直接推出结论
needCalc = false;
}
}
// 中心扩展
if(needCalc) {
while(i - 1 - record[i] >= 0 && i + 1 + record[i] < len) {
if(str.charAt(i + 1 + record[i]) == str.charAt(i - 1 - record[i])) {
record[i]++;
} else {
break;
}
}
// 更新右边界及中心
rightSide = i + record[i];
rightSideCenter = i;
// 记录最长回文串
if(record[i] > longestHalf) {
center = i;
longestHalf = record[i];
}
}
}
// 跳过之前添加的#
StringBuffer sb = new StringBuffer();
for(int i = center - longestHalf + 1; i <= center + longestHalf; i += 2) {
sb.append(str.charAt(i));
}
return sb.toString();
}
本文详细介绍了如何使用Marcher算法解决最长回文子串问题。通过添加辅助字符确定回文串中心,然后利用中心扩散法遍历字符串,优化过程中注意边界覆盖和超界问题。算法实现的关键在于维护一个记录回文子串长度的数组,确保正确处理回文串的对称特性。
7425

被折叠的 条评论
为什么被折叠?



