多图解析manacher算法原理

news2025/1/17 4:57:52

什么是manacher算法

用于快速计算一个字符串的最长回文子串

什么是最长回文子串?

例如:abc12321中,最长回文子串为12321,即子字符串中最长,且是回文的那个

怎么用暴力做法找出最长回文子串呢?

  • 长度为奇数的字符串:枚举每个位置的字符,往两边扩,一直扩到不是回文为止,记录扩的最长的那个子串
  • 长度为偶数的字符串:除了枚举每个位置的字符外,还要枚举每两个字符中间的位置,例如"1221",如果只枚举每个字符,最长的长度为1,但从两个"2"的中间位置开始扩,能计算出正确答案4

这种做法时间复杂度为O(n^2),而manacher算法能做到时间复杂度为O(n)

为了方便计算长度为偶数的字符串的最长回文子串,将原始字符串处理成左右两边加上#,且每两个字符中间也加上#

这样不管原始串长度为奇数还是偶数,都可以用枚举每个字符往外扩的方式计算最长回文子串

几个概念

  • 回文半径数组pArr:以每个字符为中心,能扩出来的的最大回文半径

  • 最右回文边界R:每个字符往左右扩时,扩的最右的位置

    • 不管是以哪个字符为中心扩的,只要比以前历次扩得更往右了,就增加最右回文边界R
  • 取得最右回文边界时的中心位置C

    • 随着R的更新而更新

流程

manacher算法的整体流程,和暴力做法很相似,都是遍历每个位置,以每个位置为中心往外扩

但是可以利用上文提到的pArr,R,C进行加速

遍历到每一个字符时,有以下的可能性:

i位置比R位置大

这种情况无法利用以前的数据优化,暴力往外扩

i位置小于等于R

假设遍历到i时,发现i小于等于R,说明和R对应的C,一定在i左边,因为一定之前i左边的一个数作为C,的最右回文边界扩到了R位置

作出一个i关于C的对称点 i*,和R关于C的对称点L,如下图所示:
在这里插入图片描述

i的回文半径可以查表得出,因为在之前遍历到i时,一定计算过i*的回文半径

接下来根据i*的回文半径分情况讨论

情况一

i*扩出来的区域,在(L,R)之间,即左边界大于L

假设i*扩出来的区域为A,作出以i位置为中心,和区域A等长度的区域B

在这里插入图片描述

由于A和B关于C对称,则A和B互为逆序

因为A为回文,根据回文的逆序也是回文这一特点,推出B也是回文

说明以i为中心扩出来的区域,至少有和A一样的长度是回文

那有没有可能更长呢?

在这里插入图片描述

假设A区域左边的字符为x,右边的字符为y

B区域左边的字符为m,右边的字符为n

当初i*没能再往两边扩,说明 x != y

根据回文的对称性,y == m,x == n,因为x不等于y,说明m不等于n,则B区域无法再往外扩

在这里插入图片描述

因此以i为中心的最长回文半径,就是i*的最长回文半径:pArr[i] = pArr[i*]

情况二

i*扩出来区域的左边界比L更小:

在这里插入图片描述

假设L关于i*对称的点位L*,L左边为a,L*右边为b

a和b是什么关系?相等,因为以i*为中心往外扩出来的区域,远不止a和b,因此a和b是关于i*对称的,相等

我们看右边,假设R关于i的对称点为R*,假设从L到L的区域为A,从R到R的区域为B

因为A和B关于C对称,因此A和B互为逆序

且L到L关与i对称,因此A为回文串,根据回文的逆序也是回文的规则,B也为回文

因此以i为中心,至少有A区域长度的回文子串

有没有可能更长呢?

接下来思考x和y是否相等

a和b在i*的回文半径内,因此a == b

而b和x回文对称,因此b == x,推出a == x

当初为什么以C为半径没能再往外扩了,就是因为a != y,而a == x, 推出x != y

说明以i为中心的最大回文串最多R,无法再往外扩,i为中心的最长回文半径,就是从iR的距离

情况三

i*扩出来区域的左边界和L重合:

在这里插入图片描述

根据上文分析的性质,关于i至少有B区域为回文串

但是会不会更大呢?不确定,需要往外扩尝试!

为什么此时不确定呢?

假设L左边的字符为a,L*右边的字符为b

R*左边的字符为x,R右边的字符为y

在这里插入图片描述

因为之前i*没能在往外扩,因此a != b,而b等于x,则a != x

因为之前C没能在往外扩,因此a != y

目前能得到的结论是:a != x,a != y,那x和y是否相等呢?无法确定!

因此到底以i为中心的最长回文子串有多长,需要通过往外扩才知道

总结

根据上面的分析,manacher算法的流程可以总结为:

  1. i位置比R位置大:暴力扩

  2. i位置小于等于R

    1. i扩出来的区域,在(L,R)之间:i的回文半径就是i的回文半径
    2. i*扩出来的区域的左边界比L更小:i的回文半径就是从i到R的距离
    3. i*扩出来区域的左边界和L重合:i的回文半径至少为从i到R的距离,至于有没有可能更长需要往外扩尝试

时间复杂度分析

对于遍历到的每个位置,一定会走上面4个分支中的一个

分支2.a和2.b直接查表pArr得到答案,耗时O(1)

分支1和2.c要么扩失败,要么会不断推高R,而R是有极限的,最多被推高O(N)次

同时扩失败的总次数也是有限的,最多为O(N)次

因此整体时间复杂度为O(N)

代码

public  static  int manacher(String s) {
    char[] str = preHandle(s);
    int[] pArr = new  int[str.length];
    int C = -1;
    int R = -1;

    for (int i = 0;i<str.length;i++) {
        // i比R大
        if (i > R) {
            int l = i;
            int r = i;
            // 不断往外扩
            while (l >= 0 && r < str.length && str[l] == str[r]) {
                R = r;
                C = i;
                pArr[i] = R - C;
                r++;
                l--;
            }
            continue;
        }
        // i小于等于R
        int _i = 2 * C - i;
        int L = 2 * C - R;
        // _i扩的左边界比L大
        if (L < _i - pArr[_i]) {
            pArr[i] = pArr[_i];
        // _i扩的左边界比L大小
        } else  if (L > _i - pArr[_i]) {
            pArr[i] = R - i;
        // _i扩的左边界和L相等
        } else {
           pArr[i] = R - i;
           int l = i - pArr[i] - 1;
           int r = R + 1;
           // 不断往外扩
           while (l >= 0 && r < str.length && str[l] == str[r]) {
                R = r;
                C = i;
                pArr[i] = R - C;
                r++;
                l--;
            }
        }
    }

    int max = Integer.MIN_VALUE;
    for (int i = 0;i<pArr.length;i++) {
        max = Math.max(max, pArr[i] * 2 + 1);
    }
    return max / 2;
}

/**
*abcba => #a#b#c#b#a#
*/
private  static  char[] preHandle(String s) {
    char[] res = new  char[2 * s.length() + 1];
    res[0] = '#' ;
    int resi = 1;
    for (int i = 0;i < s.length();i++) {
        res[resi] = s.charAt(i);
        resi++;
        res[resi] = '#' ;
        resi++;
    }

    return res;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程等待,线程休眠,线程状态

线程等待:因为线程与线程之间&#xff0c;调度顺序是完全不确定&#xff0c;它取决于操作系统本身调度器的一个实现&#xff0c;但是有时候我们希望这个顺序是可控的&#xff0c;此时的线程等待&#xff0c;就是一种方法&#xff0c;用来控制线程结束的先后顺序&#xff1b; 1)…

神经网络基础部件-激活函数详解

本文分析了激活函数对于神经网络的必要性&#xff0c;同时讲解了几种常见的激活函数的原理&#xff0c;并给出相关公式、代码和示例图。 一&#xff0c;激活函数概述 1.1&#xff0c;前言 人工神经元(Artificial Neuron)&#xff0c;简称神经元(Neuron)&#xff0c;是构成神经…

[linux] 冯诺依曼体系及操作系统的概念

文章目录1. 冯诺依曼体系结构1. 为什么要有内存&#xff1f;1. 若内存不存在2. 若内存存在结论12.在硬件层面&#xff0c;单机和跨单机之间数据流是如何流向的&#xff1f;结论22. 操作系统(Operator System)1. 概念2.如何理解操作系统对硬件管理&#xff1f;结论13.管理者和被…

Linux安装 MySQL

1、MySQL安装方式 Linux MySQL安装有很多方式&#xff1a;yum安装、apt-get安装、rpm安装、二进制安装、源码编译安装。 比较通用的做法就是&#xff0c;二进制安装、源码编译安装&#xff0c;但是源码编译安装太麻烦&#xff0c;所以一般都是选择二进制安装。本文就是采用二…

百度百科创建词条参考资料问题汇总

百度百科词条编辑规则是相当复杂的&#xff0c;不是单纯写一写百度词条内容那么简单&#xff0c;还需要准备对应的参考资料来佐证你内容的真实性&#xff0c;很多小伙伴就因为这个参考资料犯了难&#xff0c;每次词条审核不通过的原因也大部分是因为参考资料的问题。 参考资料…

爬虫攻守道 - 2023最新 - Python Selenium 实现 - 数据去伪存真,正则表达式谁与争锋 - 爬取某天气网站历史数据

前言 前面写过3篇文章&#xff0c;分别介绍了反爬措施&#xff0c;JS逆向ajax获取数据&#xff0c;以及正则表达式匹配开头、结尾、中间的用法。第3篇算是本文 Python Selenium 爬虫实现方案的子集&#xff0c;大家可以参照阅读。 另外本意是“攻守”&#xff0c;不知道为何输…

【搞懂AUTOSAR网络管理测试】AUTOSAR网络管理规范需求解读

文章目录前言一、名词解释二、NM报文1.NM报文格式2.NM报文数据场内容三、NM状态机1.NM状态转换图2.状态前言 AUTOSAR Automotive Open System Architecture&#xff0c;汽车开放系统架构&#xff0c;由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&am…

【Nginx】Nginx配置实例-负载均衡

1. 首先准备两个同时启动的 Tomcat2. 在 nginx.conf 中进行配置 1. 首先准备两个同时启动的 Tomcat 2. 在 nginx.conf 中进行配置 在 nginx.conf 中进行配置 随着互联网信息的爆炸性增长&#xff0c;负载均衡&#xff08;load balance&#xff09;已经不再是一个很陌生的话题…

中睿天下入选“2023年网络安全服务阳光行动”成员单位

近日&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;正式公布了“2023年网络安全服务阳光行动”成员单位名单。中睿天下作为以“实战对抗”为特点的能力价值型网络安全厂商&#xff0c;凭借领先的产品方案、专业的服务支持和优秀的行业自律精神成功入选。为规范…

12、Javaweb_登录添加删除修改多选删除分页查询复杂条件查询案例

1. 综合练习 1. 简单功能 1. 列表查询 2. 登录 3. 添加 4. 删除 5. 修改 2. 复杂功能 1. 删除选中 2. 分页查询 * 好处&#xff1a; 1. 减轻服务器内存的开销 …

C/C++数据类型转换详解

目录 C语言数据类型转换 1、自动类型转换 &#xff08;1&#xff09;算术表达式的自动类型转换 &#xff08;2&#xff09;赋值运算中的自动类型转换 2、强制类型转换 C数据类型转换 1、static_cast<> 2、const_cast<> 3、dynamic_cast<> 4、reint…

【Allegro软件PCB设计120问解析】第78问 如何在PCB中手动添加差分对及自动添加差分对属性呢?

答:设计PCB过程中,若设计中有差分对信号,则需要将是差分的2个信号设置为差分对,设置差分对有2种方式:手动添加及自动添加。 1、 手动添加差分对: 第一步,点击Setup-Constraints-Constraint Manager调出CM规则管理器,然后到Physical规则管理器下点击Net-All Layers,然…

React脚手架+组件化开发+组件生命周期+组件通信

react脚手架&#xff08;create-react-app&#xff09; 1.作用&#xff1a; 帮助我们生成一个通用的目录结构&#xff0c;并且已经将我们所需的工程环境配置好 2.依赖环境 脚手架都是使用node编写的&#xff0c;并且都是基于webpack的&#xff1b; 3.安装node 4.安装脚手架 n…

在哔站黑马程序员学习Spring—Spring Framework—(一)核心容器(core container)学习笔记

一、spring介绍 spring是一个大家族全家桶&#xff0c;spring技术是JavaEE开发必备技能。spring技术解决的问题&#xff1a;一是简化开发&#xff0c;降低企业级开发的复杂性。二是框架整合&#xff0c;高效整合其他技术&#xff0c;提高企业级应用开发与运行效率。 spring技术…

git revert和git reset的差异点和区别

git revert 定义 撤销某次提交,此次撤销操作和之前的commit记录都会保留。 git revert会根据commitid找到此次提交的变更内容&#xff0c;并撤销这些变更并作为一次新commit提交。 ps:此次commit和正常commit相同&#xff0c;也可以被revert revert和reset有本质的差别&#…

使用C#开发ChatGPT聊天程序

使用C#开发ChatGPT聊天程序 总体效果如下&#xff1a; 源码下载 关键点1&#xff1a;无边框窗口拖动 Window中设置AllowsTransparency"True"、 Background"Transparent"以及WindowStyle"None"这样设置的话默认窗口的标题栏为不可见状态&…

cocos tween

缓动接口Tween 属性和接口说明接口说明接口功能说明tag为当前缓动添加一个数值类型&#xff08;number&#xff09;的标签to添加一个对属性进行 绝对值 计算的间隔动作by添加一个对属性进行 相对值 计算的间隔动作set添加一个 直接设置目标属性 的瞬时动作delay添加一个 延迟时…

Spring Cloud服务发现组件Eureka

简介 Netflix Eureka是微服务系统中最常用的服务发现组件之一&#xff0c;非常简单易用。当客户端注册到Eureka后&#xff0c;客户端可以知道彼此的hostname和端口等&#xff0c;这样就可以建立连接&#xff0c;不需要配置。 Eureka 服务端 添加Maven依赖&#xff1a; <…

菜谱分享APP/基于android菜谱分享系统

摘 要随着现代生活水平的不断提升&#xff0c;人们越来越关注健康,关注美食,大部分人都希望吃得美味的同时也要吃得健康,所以,有的人喜欢在家自己动手制作美食,但是却缺少这方面的资讯来源。菜谱分享APP是一个使用Hbuildex作为手机客户端和后台服务系统的开发环境, MySQL作为后…

Node.js+Vue.js全栈开发王者荣耀手机端官网和管理后台(一)

文章目录【全栈之巅】Node.jsVue.js全栈开发王者荣耀手机端官网和管理后台(一)工具安装和环境搭建初始化项目基于ElementUI的后台管理基础界面搭建创建分类&#xff08;客户端&#xff09;创建分类&#xff08;服务端&#xff09;分类列表分类编辑分类删除子分类【全栈之巅】No…