经典算法 Manacher算法详解
内容:
1、原始问题 =》O(N^2)
2、Manacher算法 =》O(N)
1、原始问题
Manacher算法是由题目“求字符串中长回文子串的长度”而来。比如 abcdcb 的最长回文子串为 bcdcb ,其长度为5
暴力解法:
可以遍历字符串中的每个字符,当遍历到某个字符时就比较一下其左边相邻的字符和其右边相邻的字符是否相同,
如果相同则继续比较其右边的右边和其左边的左边是否相同,如果相同则继续比较……,我们暂且称这个过程为向外“扩”。
当“扩”不动时,经过的所有字符组成的子串就是以当前遍历字符为中心的长回文子串
暴力解法的时间复杂度分析:
每次遍历都能得到一个长回文子串的长度,使用一个全局变量保存最大的那个,遍历完后就能得到此题的解。
这种方法的时间复杂度:当来到第一个字符时,只能扩其本身即1个;来到第二个字符时,多扩两 个;……;
来到字符串中间那个字符时,多扩 (n-1)/2+1 个;因此时间复杂度为 1+2+……+(n-1)/2+1 即 O(N^2) 。
但是Manacher算法却能做到 O(N)
处理回文子串长度为偶数的问题:
例如abcdcb,其中bcdcb属于一个回文子串,但如果回文子串长度为偶数呢?像 cabbac ,按照上面定义的“扩”的逻辑,
岂不是每个字符的回文半径都是0,但事实上cabbac的回文子串长度是6。因为我们上面“扩”的逻辑默认是将回文子串当做奇数
长度的串来看的,因此我们在使用 Manacher算法之前还需要将字符串处理一下,这里有个技巧,就是将字符串的首尾
和每个字符之间加上一个特殊符号,这样就能将输入的串统一转为奇数长度的串了。比如 abba 处理过后为 #a#b#b#a ,
这样的话就有 charArr[4]='#' 的回文半径为4,也即原串的大回文子串长度为4。相应代码如下:
1 public static char[] manacherString(String str){
2 char[] source = str.toCharArray();
3 char chs[] = new char[str.length() * 2 + 1];
4 for (int i = 0; i < chs.length; i++) {
5 chs[i] = i % 2 == 0 ? '#' : source[i / 2];
6 }
7 return chs;
8 }
2、Manacher算法
Manacher算法中的相关概念:
- 回文半径:串中某个字符多能向外扩的字符个数称为该字符的回文半径。比如 abcdcb 中字符 d ,能扩一 个 c ,还能再扩一个 b ,再扩就到字符串右边界了,再算上字符本身,字符 d 的回文半径是3。
- 回文半径数组 pArr :长度和字符串长度一样,保存串中每个字符的回文半径。比如 charArr="abcdcb" , 其中 charArr[0]='a' 一个都扩不了,但算上其本身有 pArr[0]=1 ;而 charArr[3]='d' 多扩2个,算上 其本身有 pArr[3]=3 。
- 右回文右边界 R :遍历过程中,“扩”这一操作扩到的右的字符的下标。比如 charArr=“abcdcb” ,当遍 历到 a 时,只能扩 a 本身,向外扩不动,所以 R=0 ;当遍历到 b 时,也只能扩 b 本身,所以更新 R=1 ; 但当遍历到 d 时,能向外扩两个字符到 charArr[5]=b ,所以 R 更新为5。
- 右回文右边界对应的回文中心 C : C 与 R 是对应的、同时更新的。比如 abcdcb 遍历到 d 时, R=5 , C 就是 charArr[3]='d' 的下标 3
Manacher算法实质上是利用了遍历过程中计算的pArr、R、C来为后序字符的回文半径的求解加速:
情况1 遍历到的字符下标 cur 在 R 的右边(开始时R=-1)
在这种情况下该字符的大回文半径 pArr[cur] 的求解无法加速,只能一步步向外扩来求解。这种情况实际上只能用暴力解法,无法加速

情况2 遍历到的字符下标 cur 在 R 的左边
这时 pArr[cur] 的求解过程可以利用之前遍历的字符回文半径信 息来加速。分别做 cur 、 R 关于 C 的对称点 cur' 和 L:

- 如果从 cur' 向外扩的大范围的左边界没有超过 L ,那么 pArr[cur] = pArr[cur']
- 如果从 cur' 向外扩的大范围的左边界超过了 L ,那么 pArr[cur] = R-cur+1
- 以 cur' 为中心向外扩的大范围的左边界正好是 L ,那么 pArr[cur] >= R-cur+1(后面的还可能继续扩)
证明省略,综上所述, pArr[cur] 的计算有四种情况:暴力扩、等于 pArr[cur'] 、等于 R-cur+1 、从 R-cur+1 继续向外扩。
时间复杂度分析:
使用此算法求解原始问题的过程就是遍历串中的每个字符,每个字符都尝试向外扩到大并更新 R (只增不减),
每次 R增加的量就是此次能扩的字符个数,而 R 到达串尾时问题的解就能确定了,因此时间复杂度就是
每次扩操作检查的次数总和,也就是 R 的变化范围( -1~2N ,因为处理串时向串中添加了 N+1 个 # 字符),即O(N)
完整代码:
public class Manacher {
// 处理字符串
public static char[] manacherString(String str) {
char[] source = str.toCharArray();
char[] chs = new char[str.length() * 2 + 1];
for (int i = 0; i < chs.length; i++) {
chs[i] = i % 2 == 0 ? '#' : source[i / 2];
}
return chs;
}
// 核心代码
public static int maxPalindromeLength(String str) {
char[] charArr = manacherString(str);
int pArr[] = new int[charArr.length];
int R = -1, C = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i < charArr.length; i++) {
// 确定加速信息
pArr[i] = i > R ? 1 : Math.min(pArr[C * 2 - i], R - i);
// 继续扩
while (i + pArr[i] < charArr.length && i - pArr[i] > -1) {
if (charArr[i + pArr[i]] == charArr[i - pArr[i]]) {
pArr[i]++;
} else {
break;
}
}
// 扩完之后改变R和C
if (R < i + pArr[i]) {
R = i + pArr[i] - 1;
C = i;
}
max = Math.max(max, pArr[i]);
}
return max - 1;
}
public static void main(String[] args) {
System.out.println(maxPalindromeLength("zxabcdcbayq"));
}
}
经典算法 Manacher算法详解的更多相关文章
- JVM垃圾回收算法及回收器详解
引言 本文主要讲述JVM中几种常见的垃圾回收算法和相关的垃圾回收器,以及常见的和GC相关的性能调优参数. GC Roots 我们先来了解一下在Java中是如何判断一个对象的生死的,有些语言比如Pyth ...
- 【机器学习】【条件随机场CRF-2】CRF的预测算法之维特比算法(viterbi alg) 详解 + 示例讲解 + Python实现
1.CRF的预测算法条件随机场的预测算法是给定条件随机场P(Y|X)和输入序列(观测序列)x,求条件概率最大的输出序列(标记序列)y*,即对观测序列进行标注.条件随机场的预测算法是著名的维特比算法(V ...
- 最短路径Floyd算法【图文详解】
Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...
- [转]O(n)回文子串算法 Manacher算法
这里,我介绍一下O(n)回文串处理的一种方法.Manacher算法.原文地址:http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear- ...
- openerp经典收藏 字段定义详解(转载)
字段定义详解 原文地址:http://shine-it.net/index.php/topic,2159.0.htmlhttp://blog.sina.com.cn/s/blog_57ded94e01 ...
- openerp经典收藏 对象定义详解(转载)
对象定义详解 原文地址:http://shine-it.net/index.php/topic,2159.0.htmlhttp://blog.sina.com.cn/s/blog_57ded94e01 ...
- 一个经典的 HTTP协议详解
1引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1 ...
- c++ LeetCode(初级数组篇)十一道算法例题代码详解(一)
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/10940636.html 唉!最近忙着面试找实习,然后都是面试的很多是leetcode的算法题, ...
- KMP算法 Next数组详解
题面 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果你不知道这是什么意思也不要问,去百 ...
随机推荐
- 在U盘上安装Windows 7的详细步骤
买到苹果新款MacBook Air后大家最想干的事是什么?体验一下Mac OS X?事实告诉我们有几乎一半的人第一件想要做的事是装一个微软的Windows系统,但问题是新版的MBA已经没有光驱了,这可 ...
- ACCESS不可识别的数据库格式!
在Access07之前的数据库后缀名均为*.mdb 而连接字符串写成Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myFolder\*.mdb ;Pe ...
- Windows环境下搭建Nginx和多版本PHP共存
PHP版本众多,彼此之间兼容性不好,不同程序经常需要的不同版本的PHP版本.这里介绍如何使用NGINX让不同版本的PHP共存. 软件下载地址 与nginx整合的php需要选择Non Thread Sa ...
- android 图片解码显示流程
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/jingxia2008/article/details/32327699 问题来源 android 能 ...
- 批处理(bat)命令学习的一些总结
这篇笔记是我对批处理学习的一些总结,能在系统帮助里找到的内容我就不写了,太偏门的也不写,只写些个人感觉很好用的技巧,大部分属于整理 一.set 篇: 1.set(无开关) set .=test set ...
- DOS批处理 - 函数教程
DOS Batch - Function Tutorial What it is, why it`s important and how to write your own. Description: ...
- xml时间配置
这些星号由左到右按顺序代表 : * * * * * * * 秒 分 时 日 月 周 年 序号 说明 是否必填 允许填写的值 允许的通配符 秒 是 0-59 , - * / 分 是 0-59 , - * ...
- Java 发展历史
Java自1995诞生,至今已经20多年的历史. Java的名字的来源:Java是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名.Java语言中的许多库类名称,多与咖啡有关,如JavaBeans(咖啡豆 ...
- Revit API 加载族并生成实例图元
在Revit API中加载族可以使用Doc.LoadFamily方法,传入要加载的族文件路径名,但是这种方式有一种缺点,就是如果族文件在当前工程中没有加载的话则返回成功,如果已经加载过,则返回失败,也 ...
- zsh 使用
使用ctrl+r,弹出搜索框: bck-i-search: mac os 系统默认的终端为bash,切换该终端为zsh,可以用以下命令: chsh -s /bin/zsh 如过要切回默认终端bash, ...