最长回文子串O(n)算法
原文链接:英文版链接
首先,我们将字符串S中插入符号“#”转化成另一个字符串T。
比如:S = "abaaba",T = “#a#b#a#a#b#a#”。
为了找到最长回文字串,我们需要围绕Ti进行扩展,Ti-d...Ti+d是一个回文,很明显d是围绕Ti形成的回文的长度。
将每个回文的长度用数组P存起来,这样,P[i]就代表围绕Ti的回文长度,最长回文字串将会是P中的最大元素。
用上面的例子,我们得到的P的结果是(从左至右):
T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0
看P的结果,我们可以发现,最长回文子串就是"abaaba",从而可以得出就是P6 = 6。
插入“#”后,字符串的长度就都转化成奇数长度了(请注意:这只是为了更好的示范该算法。译者注:我发现这是为了避免类似"aa"这样的回文无法正确用P表示的情况)。
现在,想象一下给"abaaba"划一根竖线,有没有发现P的数值根据这根线中心对称?不仅仅是这个字符串,像回文"aba"也是如此,P中的这些数字也反应出了相似的对称性。这是巧合吗?有时候是,有时候不是。这个现象只在一种条件下是符合的,但不管怎么样这是一个不小的进步,这样我们就可以排除掉一些P[i]的值。
我们从一个更加包含更多回文,更能说明问题的一个例子出发,假设S = “babcbabcbaccba”。
上面的图片显示T从字符串S转化过来。假设这个状态下P已经部分完成了,竖实线表示回文“abcbabcba”的中心(C),虚线表示各自的左边缘(L)和右边缘(R),现在我们正在索引i的位置,并且围绕C它的映射是i',那么我们如何马上计算出P[i]?
假设我们已经到达索引i = 13,我们需要计算出p[13](上面的?标记的位置),我们先看看它围绕C的映射i'=9。
绿色的横线分别代表由i和i'为中心的回文所占的区域,我们发现i的镜像P[i'] = p[9] = 1,很明显,由于回文环绕其中心的对称性,我们得出P[i]也必须是1。
由于对称性,从上面很明显可以得出P[i] = p[i'] = 1,实际上,从C开始的后三个元素都可以从对称性得出它们的值(P[ 12 ] = P[ 10 ] = 0, P[ 13 ] = P[ 9 ] = 1, P[ 14 ] = P[ 8 ] = 0)。
现在我们在索引i = 15,以C为中心它的镜像是i' = 7,那么是否P[15] = P[7] = 7?
现在我们在索引i = 15,P[i]的值是多少?如果我们依据对称性,P[i]的结果应该跟P[i']同为7,但这是错误的。如果我们以T15为中心扩展,我们会得到回文“a#b#c#b#a”,它实际上比从对称性得出的结果7要短,为什么呢?
有色线条表示了以i和i'为中心的区域,实绿线表示由于对称性两者必须相同的区域,红实线表示两边可能不相同的区域,虚绿线表示超过了中心的区域
很明显两根实绿线表示的区域必须相同,超过了中心的区域也必须对称(虚绿线表示),这里必须注意P[i']是7,它超出了以C为中心的回文的左边缘L(实红线部分),这样的话它不再遵循回文的对称性了,我们只知道P[ i ] ≥ 5,为了找到P[i]的值我们必须越过右边缘(R)进行特征对比。在这里,既然P[21] != P[1],得出结论P[i] = 5。
这样我们就得出这个算法的核心部分了:
if P[ i' ] ≤ R – i,
then P[ i ] ← P[ i' ]
else P[ i ] ≥ P[ i' ]. (这里我们必须越过右边界R寻找P[i]的值)
很优雅吧?如果这句话理解了,你就理解了这个算法的核心部分了,这也是最难的部分。
最后的部分是我们该什么时候把C和R的位置往右移动,这个很简单:
如果以i为中心的回文越过了R,我们将C更新为i(回文中心),然后将R扩展为新回文的右边缘。
每移动一步,有两种可能。如果P[ i ] ≤ R – i,我们设置P[i] = p[i'],这个只需要一步。否则我们尝试将回文的中心移到i,并且从右边缘R开始扩展之。扩展R(内部循环)最多需要N步,然后定位和测试中心点也需要N步。最终,这个算法可以保证在2*N步之内完成,得到了一个线性时间解。
现在我们可以通过P获取最长回文的长度maxLen及其中间位置的索引i,那么我们应该截取哪一段字符串才是我们需要的回文子串呢。
对比一下S和T,我们会发现,如果字母A在T中的位置为n的话,那么它在S中的位置就是(n - 1)/2,那是不是截取字符串的起始位置就是(i - maxLen - 1)/2?其实不是的,注意到在T中回文的第一个字母肯定是“#”,所以我们需要先把位置往后移一位,到“#”后面的第一个字母,也就是我们需要的回文的起始字母(比如"#a#a#",我们需要的是"a#a"的起始位置而不是"#a#a#"的起始位置),那最终的结果就是(i - maxLen + 1 - 1)/2,也就是(i - maxLen)/2。
下面奉上javascript的算法。
function maxPalin(t) {
var c = 0,
R = 0,
p = [0],
s = '#' + t.split('').join('#') + '#',
maxLen = 0,
center;
for (var i = 1, len = s.length; i < len; ++i) {
var iMirror = 2*c - i;
p[i] = R > i ? Math.min(R - i, p[iMirror]) : 0;
while(s[i - 1 - p[i]] && (s[i - 1 - p[i]] === s[i + 1 + p[i]])) {
++p[i];
}
if (p[i] > R - i) {
c = i;
R = i + p[i];
}
} for (i = 1; i < len; ++i) {
if (p[i] > maxLen) {
maxLen = p[i];
center = i;
}
}
return t.substr((center - maxLen) / 2, maxLen);
}
最长回文子串O(n)算法的更多相关文章
- Leetcode 5. Longest Palindromic Substring(最长回文子串, Manacher算法)
Leetcode 5. Longest Palindromic Substring(最长回文子串, Manacher算法) Given a string s, find the longest pal ...
- 最长回文子串的Manacher算法
对于一个比较长的字符串,O(n^2)的时间复杂度是难以接受的.Can we do better? 先来看看解法2存在的缺陷. 1) 由于回文串长度的奇偶性造成了不同性质的对称轴位置,解法2要对两种情况 ...
- 51nod1089(最长回文子串之manacher算法)
题目链接: https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1089 题意:中文题诶~ 思路: 我前面做的那道回文子串的题 ...
- 求最长回文子串:Manacher算法
主要学习自:http://articles.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html 问题描述:回文字符串就是左右 ...
- 最长回文子串(Manacher算法)
回文字符串,想必大家不会不熟悉吧? 回文串会求的吧?暴力一遍O(n^2)很简单,但当字符长度很长时便会TLE,简单,hash+二分搞定,其复杂度约为O(nlogn), 而Manacher算法能够在线性 ...
- 计算字符串的最长回文子串 :Manacher算法介绍
转自: http://www.open-open.com/lib/view/open1419150233417.html Manacher算法 在介绍算法之前,首先介绍一下什么是回文串,所谓回文串,简 ...
- 51Nod 1089 最长回文子串 V2 —— Manacher算法
题目链接:https://vjudge.net/problem/51Nod-1089 1089 最长回文子串 V2(Manacher算法) 基准时间限制:1 秒 空间限制:131072 KB 分值: ...
- 51 Nod 1089 最长回文子串(Manacher算法)
1089 最长回文子串 V2(Manacher算法) 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 回文串是指aba.abba.cccbccc.aaa ...
- 最长回文子串 —— Manacher (马拉车) 算法
最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...
随机推荐
- Javascript基础系列之(五)关键字和保留字 (keyword)
关键字不可以作为变量名或者函数名 break case catch continue default delete do else finally for function if in instanc ...
- WebService学习笔记一
01——Schema约束复习 1.1 schema约束 几个重要知识: 1.namespace 相当于schema文件的id 2.targetNamespace属性 用来指定schema文件的name ...
- iBATIS sqlMapConfig配置详解
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMapConfig PUBLIC & ...
- MVC学习Day02之校验
MVC校验有两种方法: 方法一:自己动手写js---------略 方法二: l在View的页面中,首先指定页面强类型@model 类型 l使用Html.***For(model=>model. ...
- java Thread编程(三) 同步的两种不同实现方式
1,创建需要同步的对象(方式一) package concurrency; public class Bank { private double amount; public Bank(double ...
- Java_HttpURLConnection使用
包括使用HttpURLConnection执行get/post请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...
- hdu2594 KMP
2个字符长合并在一起即可.要注意next[n]的值要小于初始的两个字符串的长度; //next[]存的是之前相同的长度. //也是位置,只是s[i]不一定和s[next[i]]相同 //但是i之前的和 ...
- 《疯狂Java:突破程序员基本功的16课》读书笔记-第二章 对象与内存控制
Java内存管理分为两个方面:内存分配和内存回收.这里的内存分配特指创建Java对象时JVM为该对象在堆内存中所分配的内存空间.内存回收指的是当该Java对象失去引用,变成垃圾时,JVM的垃圾回收机制 ...
- Android Studio能干什么
建立系统工具包可以用来生成,测试,运行您的应用程序和软件包.构建系统是独立于Android的工作室,所以你可以调用它的Android的工作室或从命令行.在你写你的应用程序,你可以使用编译系统的特点: ...
- javaScript基础练习题-下拉框制作
1.基础回顾 如何让一个段javascript在文档加载后执行,(因为自己忘了,所以顺便复习一下) window.onload = function(){}; <!DOCTYPE html PU ...