内容:

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. [LeetCode&Python] Problem 412. Fizz Buzz

    Write a program that outputs the string representation of numbers from 1 to n. But for multiples of ...

  2. CSU 1112: 机器人的指令

    1112: 机器人的指令 Submit Page          Description 数轴原点有一个机器人.该机器人将执行一系列指令,你的任务是预测所有指令执行完毕之后它的位置. ·LEFT:往 ...

  3. jquery选择器总结2

    1.JQuery的概念 JQuery是一个JavaScript的类库,这个类库集合了很多功能方法,利用类库你可以用一些简单的代码实现一些复杂的JS效果. 2.JQuery实现了 代码的分离 不用再网页 ...

  4. 使用migration创建表时,出错的解决方法

    Laravel 5.4 migrate时报错: Specified key was too long error 解决问题升级MySql版本到5.5.3以上. 手动配置迁移命令migrate生成的默认 ...

  5. PipelineDB 1.0.0 发布——使用标准的pg 扩展开发模型

    PipelineDB 1.0.0 发布已经发布了,现在的已经成为一个标准的pg 扩展了,语法也有变动 create continous view 修改为了view,create streaem 修改为 ...

  6. 一个简单的批量更新oracle 数据库中 最近的服务商名称的数据

    有一个需求是这样的,我们需要更新数据库中的数据,数据时这样的 1.大约50万以上 2. 数据中有较多的重复数据 3. 需要将表中最近的代理商的名称赋值给行中的服务商名称 4. 代理商的名称可能有多个, ...

  7. 【转】linux下终端命令快捷键

    原文网址:http://daaoao.blog.51cto.com/2329117/554177 linux下使用终端不可避免. 使用终端快捷键,当然会使你如虎添翼.记住他们吧 终端快捷键 tab=补 ...

  8. ORA-10997:another startup/shutdown operation of this instance in progress解决方法

    SQL> startup ORA-10997: another startup/shutdown operation of this instance inprogress ORA-09967: ...

  9. DOMContentLoaded 和 Load 事件 区别(待补充)

    javascript会阻塞dom的解析.当解析过程中遇到<script>标签的时候,便会停止解析过程,转而去处理脚本,如果脚本是内联的,浏览器会先去执行这段内联的脚本,如果是外链的,那么先 ...

  10. Python与机器学习

    pandas里面的对于数据操作比如where,drop以及dropna等都会有一个属性:inplace,这个单词意思是原地,如果inplace=true代表数据本身要返回(原地数据也会被改变):如果i ...