最长回文子串 : Marcher算法

本文详细介绍了如何使用Marcher算法解决最长回文子串问题。通过添加辅助字符确定回文串中心,然后利用中心扩散法遍历字符串,优化过程中注意边界覆盖和超界问题。算法实现的关键在于维护一个记录回文子串长度的数组,确保正确处理回文串的对称特性。
摘要由CSDN通过智能技术生成

相应leetcode题目

leetcode 第5题. 最长回文子串
tu1

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. 右边回文子串 超过字符串长度 问题:
比如 ,
tu3
在红色圈中的字符 ‘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();
 }

参考文章

漫画:如何找到字符串中的最长回文子串?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值