layout: post

title: Manacher(马拉车)算法

date: 2019-09-07

author: xiepl1997

cover: 'assets/img/manacher.png'

tags: 敲敲敲

Manacher’s Alogrithm,中文名叫马拉车算法,是一位叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,算法的神奇之处就在于将时间复杂度精进到了O(N)。还记得在两年前的四省赛中,有一道关于回文的题,题解就是用马拉车算法做解的,然而我们没有做出来。

01 由来

在求解最长回文子串时,一般的思路是以当前字符为中心向两边扩展寻找回文,但复杂度是O(N^2),那能不能将复杂度降低到线性?马拉车算法就是为此诞生的。

02 预处理

为了在处理字符串的时候不需要为字符串长度是奇数还是偶数而分别考虑,将对原始字符串进行处理,在每一个字符的左右两边都加上特殊字符(肯定不存在原始字符串中的字符),让字符串变成一个长度为奇数的字符串。如: abba --> #a#b#b#a#

03 计算最长回文子串长度

以字符串"arddrb"为例,将预处理后的新字符串"#a#r#d#d#r#b#",作为一个新的字符串arr,定义一个辅助数组Len,Len的长度与arr等长,用Len[i]来表示以arr[i]字符为中心的最长回文半径。

在等待Len数组计算出来之后,取Len数组中值最大的数所对应的下标,就是arr字符串中的所对应的字符为中心的回文串的半径,Len数组有一个特点:Len[i]-1的值,就是原字符串中该以该字符为中心的回文串的长度。以下为i、arr、Len对应的值

i     0 1 2 3 4 5 6 7 8 9 10 11 12
arr # a # r # d # d # r # b #
Len 1 2 1 2 1 2 5 2 1 2 1 2 1

04 计算回文子串起始索引

取出Len数组中最大的值的索引i后,应该如何由得到原字符串该字符的索引呢?继续以str="arddrb"为例,有arr="#a#r#d#d#r#b#",Len[6]=5,发现6-Len(6)=1,即i-Len[i]就是arr[i]字符在原始字符串中的下标。但以str="aba"为例,arr="#a#b#a#",Len[3]=4,3-Len[3]=-1,所以str[-1]将会溢出。为了避免奇回文溢出,所以在arr的首尾再分别添加一个特殊字符,如下

i     0 1 2 3 4 5 6 7 8 9 10
arr @ # c # a # b # a # $
Len 1 1 2 1 2 1 4 1 2 1 1

可以看出,对于b字符来说,6-Len(6)=2,可以得到最长回文子串的起始索引为(i-Len(i))/2。

05 计算Len数组

第三步和第四部都是以Len数组计算完成为前提来进行的,计算Len数组就是马拉车算法的主要工作了,还是以"arddrb"为例,

i     0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
arr @ # a # r # d # d # r # b # $
Len 1 1 2 1 2 1 2 5 2 1 2 1 2 1 1

定义两个变量Mi和R,Mi是所有回文子串中,能延伸到最右端位置的那个回文子串的中心点位置,R是该回文串能延伸到最右端的位置。

当i=7时,Len[i]=5,在以位置7位中心点的回文子串中,该回文串的右边界是位置12。

当i=12时,Len[i]=2,在以位置12位中心点的回文子串中,该回文串的右边界是位置14。

所以可以得到,R=Len[i]+i

具体编程时,从左往右计算数组Len,需要分以下情况

1) 当i <= R时,首先毫无疑问Len数组中i点之前的对应的值已经求出来了,利用回文的特点,只要找到i关于Mi点对称的点j,j=Mi* 2-i,i、j在以Mi为中心的回文串的范围内[L, R]:

  • 如果Len[j] < R-i(同样是L到j的距离),说明以j为中心的回文串没有超出范围[L, R],所以,Len[i] = Len(j),如下图

  • 如果Len[j] >= R-i,即j为中心的回文串的最左端超过L,如下图所示,所以Len[i] = Len[j]是不成立的,有回文串的特性可知,Len[i] 至少等于R-i,至于是否大于R-i,那还得需要从R+1开始一一匹配,直到失配为止,从而更新R和对应的中心点Mi以及Len[i]。

2) 当i > R时,如下图,这种情况没法用到回文串的特性,只能老实地去一一匹配。

代码如下(以leetcode第5题为例)

public String longestPalindrome(String s) {
int mi = 0;
int right = 0;
int maxlength = 0;
int maxpoint = 0;
String temp = "@#";
for(int i = 0; i < s.length(); i++){
temp += s.charAt(i);
temp += "#";
}
temp += "*";
int[] p = new int[temp.length()];
for(int i = 0; i < temp.length(); i++){
p[i] = 0;
}
for(int i = 1; i < temp.length()-1; i++){
p[i] = right > i? Math.min(p[2*mi-i], right - i) : 1;
while(temp.charAt(i+p[i]) == temp.charAt(i-p[i])){
p[i]++;
}
if(i + p[i] > right){
right = i + p[i];
mi = i;
}
if(maxlength < p[i]){
maxlength = p[i];
maxpoint = i;
}
}
//(maxpoint - maxlength)/2为最长回文数的起始点,maxlength为最长回文数的长度
return s.substring((maxpoint - maxlength)/2, (maxpoint - maxlength)/2 + maxlength - 1);
}

理解p[i] = right > i? Math.min(p[2* mi-i], right - i) : 1;就将manacher理解的差不多了。

Manacher(马拉车)算法(jekyll迁移)的更多相关文章

  1. manacher(马拉车算法)

    Manacher(马拉车算法) 序言 mannacher 是一种在 O(n)时间内求出最长回文串的算法 我们用暴力求解最长回文串长度的时间复杂度为O(n3) 很明显,这个时间复杂度我们接受不了,这时候 ...

  2. HDU - 3068 最长回文manacher马拉车算法

    # a # b # b # a # 当我们遇到回判断最长回文字符串问题的时候,若果用暴力的方法来做,就是在字符串中间添加 #,然后遍历每一个字符,找到最长的回文字符串.那么马拉车算法就是在这个基础上进 ...

  3. Manacher (马拉车) 算法:解决最长回文子串的利器

    最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...

  4. manacher马拉车算法

    Manacher算法讲解 总有人喜欢搞事情,出字符串的题,直接卡掉了我的40分 I.适用范围 manacher算法解决的是字符串最长回文子串长度的问题. 关键词:最长 回文 子串 II.算法 1.纯暴 ...

  5. 最长回文子串 —— Manacher (马拉车) 算法

    最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...

  6. Manacher(马拉车)算法

    Manacher算法是一个求字符串的最长回文子串一种非常高效的方法,其时间复杂度为O(n).下面分析以下其实行原理及代码: 1.首先对字符串进行预处理 因为回文分为奇回文和偶回文,分类处理比较麻烦,所 ...

  7. [模板] Manacher(马拉车)算法

    用途 求回文子串 做法 先考虑回文子串以某字符为中心的情况,即长度为奇数 推着做,记rad[i]为以i位置为中心的最大半径(包含中点) 考虑怎么求rad[i].找之前的一个右端点最靠右的位置p,设它的 ...

  8. Manacher(马拉车)算法详解

    给定一个字符串,求出其最长回文子串 eg:  abcba 第一步: 在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里). 如  原来ma  /*  a    b a    b   c ...

  9. Manacher's Algorithm 马拉车算法

    这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...

随机推荐

  1. Dresdon简介

    很久没有写文章了.这几年经历了很多事情:离开VMware的不舍,拿到融资的开心,重回VMware的亲切,以及不再争强好胜,只做自己喜欢事情的平和. 可以说,我是幸运的:我这一辈子都在选择,而不是被迫接 ...

  2. “git pull” 强制覆盖本地文件

    放弃本地修改,使用服务器代码覆盖本地的Git命令如下: $ git fetch --all $ git reset --hard origin/master $ git pull 使用master分支 ...

  3. 设计模式:state模式

    核心: 把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化 例子: class State //状态接口 { public: virtual void show() = 0; ...

  4. Linux文件搜索

    一.whereis及which命令 这两个命令用来搜索命令的路径(也遵循/etc/updatedb.conf配置文件的筛选规则) whereis 命令名                        ...

  5. 一起学Blazor WebAssembly 开发(1)

    最近blazor的WebAssembly 正式版出来了,正好手头有一个项目采用的前后端分离模式做的,后端用的abp vnext(.net core 的一个很著名的框架)框架开发的,其实前端之前考虑的使 ...

  6. 题解 CF585F 【Digits of Number Pi】

    考虑用数位 \(DP\) 来统计数字串个数,用 \(SAM\) 来实现子串的匹配. 设状态 \(f(pos,cur,lenth,lim,flag)\),表示数位的位数,在 \(SAM\) 上的节点,匹 ...

  7. vue history路由模式 Nginx 生产实践

    nginx(带二级目录的配置) location ~* /A {    alias  /opt/nginx-1.4.7/html/ued/A;     try_files $uri $uri /A/s ...

  8. 云原生时代高性能Java框架—Quarkus(二)

    --- *构建Quarkus本地镜像.容器化部署Quarkus项目* Quarkus系列博文 Quarkus&GraalVM介绍.创建并启动第一个项目 构建Quarkus本地镜像.容器化部署Q ...

  9. HTTP的实体数据

      数据类型表示实体数据的内容是什么,使用的是MIME    type,相关的头字段是Accept和Content-Type:  text:即文本格式的可读数据,我们最熟悉的应该就是text/html ...

  10. File类的基本概念与递归

    一.File类 1.概念 File类:是文件和目录路径名的抽象表示形式. 即,Java中把文件或者目录(文件夹)都封装成File对象.也就是说如果我们要去操作硬盘上的文件,或者文件夹只要找到File这 ...