【Manacher算法】

  这个算法用来找出一个字符串中最长的回文子字符串。

  如果采取暴力解最长回文子字符串问题,大概可以有两种思路:1. 遍历出所有子字符串找其中最长的回文 2. 从每个字符作为中心,向两边扩散看是否回文。 第二种比第一种稍微高明一点,但是总体的复杂度还是O(n^2)的。

  而Manacher算法可以做到O(n)时间复杂度,O(n)空间复杂度。

■  思路&描述

  回文字符串有一个比较麻烦的地方,就是回文串有偶回文和奇回文两种,分别举例ABBA和ABCBA。这种区别可能要让我们在程序中额外伸出一个判断分支来,不是很方便。所以Manacher算法的第一步就是预处理字符串,将原先的字符串所有字符中间再加上头尾两端都加上一个特殊符号,这样就可以把所有可能的回文串都变成了奇回文串,方便处理:

  如ABBA变成#A#B#B#A#,ABCBA变成#A#B#C#B#A#。此外,一般实践中为了保证边界上也能统一,还会额外在头上(理论上字符串尾也可以加,但是目前尾巴上肯定是#,大不了我们遍历的时候最后这轮以这个#字符为基础的遍历不去做了,这样就可以避免尾部边界出错)加上一个$或者其他有别于#的特殊符号表示字符串的开始。经过这样预处理之后的字符串可以拿来被manacher算法处理。

  接下来,基本思路肯定还是要找出以每个字符为中心时,回文串最长可以做到多少。只是一个个去遍历太暴力了,这里可以借鉴一下动态规划中的一点小心思,也就是说我们能不能利用已有的信息(当然这部分信息是需要在之前的分析过程中手动保存下来的)来更加方便地推出我们未知的信息。

  比如我们可以创建一个数组p,针对被加工过字符串s,p的长度被设定为和s等长,且p中保存的内容,是以对应原s字符串中那个字符为中心,其最大回文子字符串的半径长度。由于s中所有回文串都是奇回文串,所以我们所说的半径是指从回文串的左端开始到中心(算入中心)的长度。如#a#b#a#的半径是4,#a#b#b#a#的半径是5。

  那么如何基于一些现有信息推断新的信息呢?考虑这样一种情形:假如我们以i变量作为向右遍历的下标,逐渐向右遍历确定了一个回文子串。这个子串的中心和半径两个参数都是可以明确的。不妨称中心的下标为id,称回文串最右端下标为mx(这也是后续编码过程中需要使用的两个辅助变量)。确定完id和mx之后我们继续往右推移i。

  当我们来到一个新的i时,如果它还在mx范围内,此时可以注意到,其实存在一个点j,j和i关于id对称,由于id,mx的回文特性,所以j的回文串很大可能就是i的回文串。这种可能成为确定只需要一个条件,那就是p[j]的取值,没有超出id,mx这个回文串的范围。下面是盗来的图,说明了i,j,id,mx以及mx'(mx关于id的对称点)的布局。

  

  那么如果p[j] > j - mx',即j对应的回文串已经超出了mx的范围,此时改怎么办?很显然,在mx'到j的这段内容,由于id,mx的回文性,还是可以对应到i到mx这段内容的。至于mx之后的内容,只能再去一个个字符遍历过去看能不能符合回文。

  总的来说,当i仍然小于mx时,i的取值应该可以取min(p[j],mx-i),当取p[j]的时候,p[i]就是p[j]的值。当取mx-i的时候,mx-i还只是一个基础值,还需要进一步处理来获得准确值。

  还没有讨论完,刚才说的都是i<mx的情况,如果此时已经遍历到超过mx了怎么办?此时由于没有任何既存的回文串性质可以利用,所以只能老老实实向外一个个字符扩散判断回文性。比较好的一点是,之前讨论过的关于i<mx的两种取值可能,也都可以(或必要)做这个扩散。

  为了保证递推的连续性,不论上上述哪种可能,获得到i的回文串之后,都需要将id和mx进行更新。应该注意,id和mx并不是我们最后要求的最长回文子串的属性,而只是当前已经遍历过的最靠右的一个回文子串的属性。

  而要求最长回文子串的属性我们可以再维护一个类似于longestInfo之类的变量,每找出一个回文子串后判断它是不是最长的。所有循环结束后去这个变量里面取值就好了。

■  编码实现

  下面是manacher算法的python实现:

def longestPalindrome(s):
"""
:type s: str
:rtype: str
"""
s = '$#' + '#'.join(list(s)) + '#' # 预处理
p = [0] * len(s)
longestInfo = [0,0]
i,currRes,currMax = 1,0,0 # 由于s[0]是$,所以i从1开始取值。id和mx分别换了个名字currRes和currMax
while i < len(s):
if i > currMax: # i已经超出mx的情况
p[i] = 1
else: # i未超出mx,再分成两种情况,体现在min函数中
j = 2*currRes - i
p[i] = min(p[j],currMax-i)
# 此时p[i]并不一定已经正确,除了决定p[i]=p[j],其他两个分支都只是给了p[i]一个基准值
while i-p[i]>0 and i+p[i]<len(s) and s[i-p[i]] == s[i+p[i]]:
# 进行一个个字符向外扩散的回文性检查
# 注意为了防止越界访问,还要有边界判断条件
p[i] += 1
# 这时p[i]才得到了最终确定的值 接下来就是将其相关属性与已有的currRes和currMax比较,看是否需要更新
if i + p[i] > currMax:
# 更新id和max
currRes = i
currMax = i + p[i] - 1 if p[i] > longestInfo[1]:
# 更新最终结果值
longestInfo = i,p[i] i += 1
center = longestInfo[0]
radius = longestInfo[1] - 1 # 注意,半径把对称中心本身算进去了,所以减一
return s[center-radius:center+radius+1].replace('#','') # 直接replace去掉所有辅助字符,得到的就是原字符串的结果了。

【Manacher算法】最长子回文串的更多相关文章

  1. Manacher 算法-----o(n)回文串算法

    回文的含义是:正着看和倒着看相同,如abba和yyxyy        Manacher算法基本要点:用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插 ...

  2. manacher算法,求回文串

    用来求字符串最长回文串或者回文串的总数量 #include<map> #include<queue> #include<stack> #include<cma ...

  3. Manacher算法,最长回文串

    给你10000长度字符串,然你求最长回文字串,输出长度,暴力算法肯定超时 #include <iostream> #include <string> #include < ...

  4. Manacher 计算最长回文串

    转自 http://blog.sina.com.cn/s/blog_3fe961ae0101iwc2.html 寻找字符串中的回文,有特定的算法来解决,也是本文的主题:Manacher算法,其时间复杂 ...

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

    一.相关介绍 最长回文子串 s="abcd", 最长回文长度为 1,即a或b或c或d s="ababa", 最长回文长度为 5,即ababa s="a ...

  6. POJ 3376 Finding Palindromes(manacher求前后缀回文串+trie)

    题目链接:http://poj.org/problem?id=3376 题目大意:给你n个字符串,这n个字符串可以两两组合形成n*n个字符串,求这些字符串中有几个是回文串. 解题思路:思路参考了这里: ...

  7. Manacher(最长镜面回文串)

    I - O'My! Gym - 101350I Note: this is a harder version of Mirrored string I. The gorillas have recen ...

  8. Manacher(最长回文串)

    http://acm.hdu.edu.cn/showproblem.php?pid=3068 最长回文 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符 ...

  9. Manacher算法----最长回文子串

    题目描述 给定一个字符串,求它的最长回文子串的长度. 分析与解法 最容易想到的办法是枚举所有的子串,分别判断其是否为回文.这个思路初看起来是正确的,但却做了很多无用功,如果一个长的子串包含另一个短一些 ...

随机推荐

  1. BZOJ.4399.魔法少女LJJ(线段树合并)

    BZOJ 注意\(c\leq7\)→_→ 然后就是裸的权值线段树+线段树合并了. 对于取\(\max/\min\)操作可以直接区间修改清空超出范围的值,然后更新到对应位置上就行了(比如对\(v\)取\ ...

  2. MySql基础笔记(二)Mysql语句优化---索引

    Mysql语句优化--索引 一.开始优化前的准备 一)explain语句 当MySql要执行一个查询语句的时候,它首先会对语句进行语法检查,然后生成一个QEP(Query Execution Plan ...

  3. vue插槽,也就是子页面、父页面相互传值的另一写法

    父页面: <template> <div class="parent"> <p>父组件</p> <child> < ...

  4. Python print函数用法,print 格式化输出

    原文地址:http://blog.csdn.net/zanfeng/article/details/52164124 使用print输出各型的 字符串 整数 浮点数 出度及精度控制 strHello ...

  5. python之进程和线程

    1 操作系统 为什么要有操作系统 ? 操作系统位于底层硬件与应用软件之间的一层 工作方式:向下管理硬件,向上提供接口 操作系统进程切换: 出现IO操作 固定时间 2 进程和线程的概念 进程就是一个程序 ...

  6. JS_高程3.基本概念(5)语句

    1.if语句 2.do-while语句:后测循环语句,循环体内的代码至少执行一次. 3.while语句:前测循环语句. 4.for语句:前测循环语句. 注意:在ECMAScript中不存在块级作用域, ...

  7. HDFS JournalNode 故障

    背景 某天凌晨四点左右,HBase RegionServer 宕机自动拉起,查看日志发现是HDFS 在进行HA切换,15次重试仍连不上可写的active,于是挂了.所以根本问题是hdfs. 日志定位 ...

  8. .NET开源Protobuf-net组件葵花手册

    一.前言 我们都知道 protobuf是由Google开发的一款与平台无关,语言无关,可扩展的序列化结构数据格式,可用做数据存储格式, 通信协议 ! 在前面<.NET开源Protobuf-net ...

  9. delphi ListView 设置固定列宽

    object Form1: TForm1 Left = Top = Caption = 'Form1' ClientHeight = ClientWidth = Color = clBtnFace F ...

  10. Java Web 清除缓存

    res.setHeader("Cache-Control", "no-cache"); res.setHeader("Pragma", &q ...