内容:

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算法详解的更多相关文章

  1. JVM垃圾回收算法及回收器详解

    引言 本文主要讲述JVM中几种常见的垃圾回收算法和相关的垃圾回收器,以及常见的和GC相关的性能调优参数. GC Roots 我们先来了解一下在Java中是如何判断一个对象的生死的,有些语言比如Pyth ...

  2. 【机器学习】【条件随机场CRF-2】CRF的预测算法之维特比算法(viterbi alg) 详解 + 示例讲解 + Python实现

    1.CRF的预测算法条件随机场的预测算法是给定条件随机场P(Y|X)和输入序列(观测序列)x,求条件概率最大的输出序列(标记序列)y*,即对观测序列进行标注.条件随机场的预测算法是著名的维特比算法(V ...

  3. 最短路径Floyd算法【图文详解】

    Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...

  4. [转]O(n)回文子串算法 Manacher算法

    这里,我介绍一下O(n)回文串处理的一种方法.Manacher算法.原文地址:http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear- ...

  5. openerp经典收藏 字段定义详解(转载)

    字段定义详解 原文地址:http://shine-it.net/index.php/topic,2159.0.htmlhttp://blog.sina.com.cn/s/blog_57ded94e01 ...

  6. openerp经典收藏 对象定义详解(转载)

    对象定义详解 原文地址:http://shine-it.net/index.php/topic,2159.0.htmlhttp://blog.sina.com.cn/s/blog_57ded94e01 ...

  7. 一个经典的 HTTP协议详解

    1引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1 ...

  8. c++ LeetCode(初级数组篇)十一道算法例题代码详解(一)

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/10940636.html 唉!最近忙着面试找实习,然后都是面试的很多是leetcode的算法题, ...

  9. KMP算法 Next数组详解

    题面 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果你不知道这是什么意思也不要问,去百 ...

随机推荐

  1. Dubbo 版 Helloworld

    使用工具:MAVEN.IDEA.Spring.Dubbo.Zookeeper 直接上代码 项目结构: 步骤如下: 搭建MAVEN项目,添加相关依赖 pom.xml <!--Zookeeper-- ...

  2. 压力测试命令行工具SuperBenchmarker

    压力测试命令行工具SuperBenchmarker SuperBenchmarker 是ㄧ个开源的类似于Apache ab的压力测试命令行工具.可以在 .NET 4.52+ 或者 .NET Core ...

  3. 《DSP using MATLAB》Problem 4.4

  4. 《DSP using MATLAB》Problem 4.1

    用到的z变换的计算公式: 代码: %% ------------------------------------------------------------------------ %% Outp ...

  5. Apache2.4配置(全)

    http://blog.csdn.net/u012291157/article/details/46492137

  6. mac 使用

    普通键盘操作mac电脑,快捷键: 快捷键 功能 ctrl + a 到行首 ctrl + e 到行尾 ctrl + up 打开任务控制(窗口平铺) window + tab 选择任务 ctrl + le ...

  7. bzoj1055玩具取名

    区间dp.记录可行性即可. #include<iostream> #include<cstdio> #include<cstring> using namespac ...

  8. tyvj1659中中救援队

    题目:http://www.joyoi.cn/problem/tyvj-1659 发现每条边要走两次,每个点要走它连接的边数次. 所以把边的权值赋成 本身的值+两个端点的点权,求最小生成树即可. !边 ...

  9. JavaScriptSerializer类 对象序列化为JSON,JSON反序列化为对象 。

    JavaScriptSerializer 类由异步通信层内部使用,用于序列化和反序列化在浏览器和 Web 服务器之间传递的数据.说白了就是能够直接将一个C#对象传送到前台页面成为javascript对 ...

  10. git 不能拉取时,检查是不是被杀毒软件给干掉了

    我这儿是 \Git\bin\sh.exe 被干掉了. 添加排除,并从隔离区中还原.