Manacher 算法——Leetcode 5.最长回文子串

在了解之前,我们先要了解什么是回文串,什么是回文子串。

回文串和回文子串:

        回文串是指一个字符串正序遍历和反向遍历结果相同的字符串。如 ABBA,正着读反着读结果是一样的。

有了回文串的概念,回文子串的概念也就显而易见了。也就是一个字符串的子字符串是回文串,则该子字符串被称为回文子串。

有了这些概念,我们就能介绍Manacher算法。在我们看实际例题前,我们先来看看什么是Manacher算法。

Manacher算法介绍:

Manacher算法是一个用来查找一个字符串中的最长回文子串(不是最长回文序列)的线性算法。它的优点就是把时间复杂度为O(n^2)的暴力算法优化到了O(n)。

既然是一个寻找字符串的最长回文子串,我们就能想到一种暴力的解法:通过从一个字符串中每个字符的位置开始,向两边扩展,一次一位。当左右指针相对应的字符相同时,那么我们就初步得到了一个回文子串。当我们对整个字符串都进行这样的操作时,我们就能得到最长的回文子串。

这样做的时间复杂度是O(n^2),也是我们想要优化的对象。

下面放出例题和我们通过暴力的解法的代码:

例题:

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成
代码:
class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        String max = s.substring(0,1);
        for(int i=0;i<len-1;i++){
            String odd = new String();
            String even = new String();
            if(s.charAt(i)==s.charAt(i+1)) even = extend(s,i,i+1);
            odd = extend(s,i,i);
            if(odd.length() > max.length()) max = odd;
            if(even.length() > max.length()) max = even;
        }
        return max;
    }
    public String extend(String s,int l,int r){
        int len = s.length();
        String ans = new String();
        while(l>=0 && r<s.length()){
            if(s.charAt(l) == s.charAt(r)){
                if(l==r) ans+=s.charAt(l);
                else{
                    ans = s.charAt(l)+ans;
                    ans = ans + s.charAt(r);
                }
                l--;
                r++;
            }
            else{
                break;
            }
        }
        return ans;
    }
}

当我们遇到ABA这样的字符串是,我们直接扩展得到的结果是正确的131。但是当遇到ABBA这样的偶数长度是,我们得到的结果却是1111,显然这样是错误的。因此,我们对此专门做了分类讨论:

当此处的字符与下一个相同时,我们扩展是从这两个同时扩展的。也就是解决了偶数的问题。‘

当此处的字符与下一个不相同的时候,我们将会从当前的字符直接开始扩展,也就是奇数的问题。

这样做虽然解决了找寻最长回文子串的问题,但是我们会发现每次向外扩展就是O(N)的时间复杂度。每次从一个字符开始向外扩展,也就是一共花费O(N^2)的时间复杂度。

那么有没有更好的方法呢?下面来介绍Manacher算法

Manacher算法:

首先我们要解决的是将麻烦的奇偶情况分类讨论的问题。我们显然可知,偶数的情况相较于奇数的情况更加复杂,因为奇数只需要从此处开始向两边扩展即可。因此我们要想办法将偶数串变成奇数串,同时不影响判断。所以我们需要预处理。

预处理:

假设一个字符串长度为n,无论n是奇是偶,2*n+1的结果一定是奇数,这是毋庸置疑的。所以我们只需将原字符串长度变成2n+1即可。在不影响原串的前提下,我们需要插入n+1个无关字符。插在哪里不影响呢?当然是间隙之间。比如插入'*' ,'$' ,'#' 这种无关字符。这样一来,我们就得到了一个奇数长度的字符串。

Manacher算法核心:

为了介绍Manacher算法,我们在此需要引入几个概念:

Manacher字符串:经过Manacher预处理的字符串
回文半径:回文字符串的中心字符到两端距离。

最右回文边界R:在遍历字符串时,每个字符遍历出的最长回文子串都会有个右边界,而R则是所有已知右边界中最靠右的位置,也就是说R的值是只增不减的。

回文中心C:取得当前R的第一次更新时的回文中心。由此可见R和C时伴生的。

半径数组:这个数组记录了原字符串中每一个字符对应的最长回文半径。

## 此处因为Manacher字符串长度是 2n+1 ,因此它的半径数组记录的其实是原字符串回文子串的回文直径,也就是回文子串的长度。

初始化 R C均为-1,然后从0处遍历字符串。同时创建半径数组。这里有点与概念相差的小偏差,就是R实际是最右边界位置的右一位。因为在计算时R包含了C的位置,导致pArr[i]+i重复计算了一次i的位置。

这时会遇到三种情况:

此处参考了 https://www.cnblogs.com/cloudplankroader/p/10988844.html 的图片和思想。

1️⃣ i > R ,也就是i在R外,此时没有什么花里胡哨的方法,直接暴力匹配,此时记得看看C和R要不要更新。

2️⃣ i <= R,也就是i在R内,此时分三种情况,在讨论这三个情况前,我们先构建一个模型

L是当前R关于C的对称点,i'是i关于C的对称点,可知 i' = 2*C - i,并且我们会发现,i'的回文区域是我们已经求过的,从这里我们就可以开始判断是不是可以进行加速处理了

情况1:i'的回文区域在L-R的内部,此时i的回文直径与 i' 相同,我们可以直接得到i的回文半径,下面给出证明

红线部分是 i' 的回文区域,因为整个L-R就是一个回文串,回文中心是C,所以i形成的回文区域和i'形成的回文区域是关于C对称的。

情况2:i'的回文区域左边界超过了L,此时i的回文半径则是i到R,下面给出证明

 

首先我们设L点关于i'对称的点为L',R点关于i点对称的点为R',L的前一个字符为x,L’的后一个字符为y,k和z同理,此时我们知道L - L'是i'回文区域内的一段回文串,故可知R’ - R也是回文串,因为L - R是一个大回文串。所以我们得到了一系列关系,x = y,y = k,x != z,所以 k != z。这样就可以验证出i点的回文半径是i - R。

情况3:i' 的回文区域左边界恰好和L重合,此时i的回文半径最少是i到R,回文区域从R继续向外部匹配,下面给出证明

因为 i' 的回文左边界和L重合,所以已知的i的回文半径就和i'的一样了,我们设i的回文区域右边界的下一个字符是y,i的回文区域左边界的上一个字符是x,现在我们只需要从x和y的位置开始暴力匹配,看是否能把i的回文区域扩大即可。

总结一下,Manacher算法的具体流程就是先匹配 -> 通过判断i与R的关系进行不同的分支操作 -> 继续遍历直到遍历完整个字符串

 简单来说,我们需要在暴力算法的基础上利用到以前的结果。如果i<R,那么说明还在范围内,由之前的结果作参考,遍历的范围小。否则只能从1开始逐步遍历。

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        char[] tchs = s.toCharArray();
        char[] chs = new char[tchs.length*2+1];
        int idx = 0;
        for(int i=0;i<chs.length;i++){
            chs[i] = (i & 1) == 1 ? tchs[idx++] : '#';
        }
        int[] pArr = new int[chs.length];
        int R = -1;
        int C = -1;
        int max = Integer.MIN_VALUE;
        int index = -1;
        for(int i=0;i<chs.length;i++){
            pArr[i] = i < R ? Math.min(pArr[2*C-i],R-i) : 1;
            while(i-pArr[i] >= 0 && i+pArr[i]<chs.length){
                if(chs[i+pArr[i]] == chs[i-pArr[i]]){
                    pArr[i]++;
                }else{
                    break;
                }
            }
            if(i+pArr[i] > R){
                R = i + pArr[i];
                C = i;
            }
            if(pArr[i] >= max){
                max = pArr[i];
                index = i;
            }
        }
        String ans = new String();
        for(int i=index-max+1;i<=index+max-1;i++){
            if(chs[i] != '#') ans += chs[i];
        }
        return ans;
    }
}

当我们的 i 还在最右侧的内部时,我们只需要从

Math.min(pArr[2*C-i],R-i)

中的最小值开始遍历就行了,很好理解,此处不展开。就是上面的3种情况。否则只能从1重新开始。遍历结束后,如果现有的范围已经超过原R,扩展R同时改变C为当前的C。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/451994.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32的DMA搬运串口数据

简介&#xff1a; 最近在学习stm32的外设初始化过程中&#xff0c;学到DMA这个外设的时候&#xff0c;还是花费了不少时间&#xff0c;特此记录一下。 实验&#xff1a;配置DMA搬运UART1的数据 &#xff0c;串口调试工具给单片机发送数据&#xff0c;然后单片机回发给串口调试…

Python实现企业微信自动打卡程序二:跳过节假日,随机打卡时间,定时任务,失败通知

实现打卡时间随机范围 既然我们程序写完后需要定时执行&#xff0c;那定时执行打卡就会导致每次上班或下班打卡时都是同一时间&#xff0c;这并不好&#xff0c;为了避免被发现&#xff0c;每次打卡时间都是同一时间&#xff0c;这里我们优化程序&#xff0c;增加随机等待时间来…

CSS元素显示模式

CSS元素显示模式 定义&#xff1a;元素显示模式是指元素&#xff08;即标签&#xff09;以什么方式进行显示。 HTML元素分为块元素和行内元素 块元素 常见块元素 &#xff08;下列仅举出部分&#xff09; <h1>~<h6>、<p>、<div>、<ul>、<…

进程间通信——IPC(Linux)

进程间通信 前言一、管道1. 管道原理2. 匿名管道①理解匿名管道②创建匿名管道——pipe③模拟实现进程池——管道 3. 命名管道①理解命名管道②使用命名管道——mkfifo拓展 —— 日志俩无关进程通信 3. 小结①管道总结②拓展命令和接口 二、System V1. 共享内存①原理②使用共享…

9、设计模式之组合模式(Composite)

一、什么是组合模式 组合模式也成为整体部分模式&#xff0c;是一种结构型设计模式。它将对象组合成树形的层次结构&#xff0c;用来表示“整体-部分”的关系。通过组合模式&#xff0c;我们可以使用相同的方式处理单个对象和多个对象组合。 二、角色组成 组件&#xff08;Com…

Unity URP 如何写基础的几何着色器

这是使用几何着色器在点中心生成一个点并根据这个点把原本的面片分成三个三角形的操作。 对于几何着色器构造相对简单&#xff0c;网上的信息也相对较多&#xff0c;需要注意的点就是需要提供一个新的数据结构供几何着色器输出&#xff0c;因为几何着色器在顶点之后&#xff0…

properties文件和yml文件的区别以及文件优先级

properties文件和yml文件的区别 yml是按照缩进关系&#xff0c;而properties用"."来表示关系springboot默认生成的是properties文件当properties文件和yml文件都存在时&#xff0c;properties文件的优先级更高。 properties文件的样式 yml文件的样式 文件优先级 r…

使用 Jenkins 和 Spinnaker 构建 Kubernetes CI/CD

无论您是新手还是持续集成和持续交付以及容器化领域的经验丰富&#xff0c;本文都将为您提供设置 Spinnaker 以满足您的软件应用程序交付需求的基本知识。 了解 Jenkins、Spinnaker 和 Kubernetes Kubernetes 和 Jenkins 是两个强大的工具&#xff0c;它们相互配合&#xff0…

新加坡大带宽服务器托管优势

在数字化快速发展的今天&#xff0c;服务器托管成为企业拓展业务、提高服务质量的关键环节。而新加坡作为一个国际性的金融、贸易和科技创新中心&#xff0c;其大带宽服务器托管服务在全球范围内享有盛誉。本文将为您科普新加坡大带宽服务器托管的诸多优势。 首先&#xff0c;新…

第十五届蓝桥杯(Web 应用开发)模拟赛 3 期-大学组(被题目描述坑惨了)

目录 1.创意广告牌 2.原子化css 3.神秘咒语 4.朋友圈 5.美食蛋白揭秘 6.营业状态变更 7.小说阅读器 8.冰岛人 9.这是一个”浏览器“ 10.趣味加密解密 总结 1.创意广告牌 这个题目不多说了&#xff0c;只要知道这些css应该都能写出来&#xff0c;不会的平时多查查文…

Docker部署ChatGLM3、One API、FastGPT

创建并运行chatglm3容器 docker run --name chatglm3 -p 8000:8000 registry.cn-hangzhou.aliyuncs.com/ryyan/chatglm.cpp:chatglm3-q5_1 创建并运行one-api容器 (其中挂载路径 D:\one-api 可以选择你自己喜欢的目录) docker run --name oneapi -d -p 3000:3000 -e TZAsia…

k8s基本使用(namespace,pod增删查)-持续更新中

目录 1. 查看Namespace 2. 创建Namespace 2.1 使用纯命令行创建 2.2 编写yaml文件创建 3. 删除Namespace 3.1 使用纯命令行删除 3.2 使用yaml文件删除 二、Pod 1. 查看pod 1.1 查看默认空间的pod 1.2 查看指定空间的pod 1.3 查看全部pod 1.4 查看pod更多信息 1…

HUAWEI 华为交换机 配置 MAC 地址漂移检测示例

组网需求 如 图 2-17 所示&#xff0c;网络中两台 LSW 间网线误接形成了网络环路&#xff0c;引起 MAC 地址发生漂 移、MAC 地址表震荡。 为了能够及时检测网络中出现的环路&#xff0c;可以在 Switch 上配置 MAC 地址漂移检测功能&#xff0c; 通过检测是否发生MAC 地址漂移…

Python in Visual Studio Code 2024年3月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布 2024 年 3 月发布适用于 Visual Studio Code 的 Python 和 Jupyter 扩展&#xff01; 此版本包括以下公告&#xff1a; 新的“Add Imports”代码操作设置调试 Django 或 Flask 应用时自动启动浏览器Python REPL 的 Shell 集成对本…

Python学习:基本数据类型

Python3 命令行参数 Python 提供了 getopt 模块来获取命令行参数。 %> python test.py arg1 arg2 arg3Python 中也可以所用 sys 的 sys.argv 来获取命令行参数&#xff1a; sys.argv 是命令行参数列表。 len(sys.argv) 计算命令行参数个数。 sys.argv[0] 表示脚本名。tes…

docker ENTRYPOINT [“sh“,“-c“,“java“,“-jar“,“Hello.jar“] 启动失败问题分析

因为没系统的学过linux语法&#xff0c;所以才会产生如下疑问。大佬请跳过。 问题&#xff1a;当在dockerfile里面配置 ENTRYPOINT ["sh","-c","java","-jar","Hello.jar"] &#xff0c;启动对应容器时会无法正常运行&…

剑指offer经典题目整理(四)

一、树的子结构 1.链接 树的子结构_牛客题霸_牛客网 (nowcoder.com) 2.描述 给两颗二叉树A B&#xff0c;判断B是不是A的子结构 3.思路 将问题拆解开来&#xff0c;首先是找到a树中子结构的位置&#xff0c;然后是判断是否相同&#xff0c;也就是说&#xff0c;我们需要去…

【小白学机器学习8】统计里的自由度

目录 1 自由度 /degree of freedom / df 1.1 物理学的自由度&#xff08;摘自网上 ^&#xff09; 1.2 数学里的自由度 1.2.2 统计里的自由度 1.2.3 需要补充线性代数的&#xff0c;用线性代数来理解自由度 1.3 统计学里自由度的定义 1.4 自由度的公式 1.5 线性回归公式…

stm32使用时钟生成PWM时调用__HAL_TIM_SetAutoreload导致PWM消失处理

stm32使用时钟生成PWM时调用__HAL_TIM_SetAutoreload导致PWM消失处理 这一个是配置的时候没有使用影子寄存器导致的, 如果加载的Autoreload的值比原来的这一个值小, 这是会出现一个问题, 如果计数器里面的值记为Count, 如果改变的时候New_Autoreload < Count < Old_Auto…

结构体的增删查改

结构体&#xff0c;是为了解决生活中的一些不方便利用c语言自带数据类型来表示的问题。例如表示一个学生&#xff0c;那么学生这个个体假如用c语言自带数据类型怎么表示呢。可以使用名字&#xff0c;也就是字符数组&#xff1b;也可以使用学号&#xff0c;也就是int类型。但是这…