最长回文子串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.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...
随机推荐
- VMware v12.1.1 专业版以及永久密钥
热门虚拟机软件VMware Workstation 现已更新至v12.1.1 专业版!12.0属于大型更新,专门为Win10的安装和使用做了优化,支持DX10.4K高分辨率显示屏.OpenGL 3.3 ...
- android常用的弹出提示框
我们在平时做开发的时候,免不了会用到各种各样的对话框,相信有过其他平台开发经验的朋友都会知道,大部分的平台都只提供了几个最简单的实现,如果我们想实现自己特定需求的对话框,大家可能首先会想到,通过继承等 ...
- c#中的protected和internal
protected限制子类访问,可以跨程序集 internal 限制此程序集访问,可以跨类 protected internal 限制此程序集的子类中访问
- Day10
会议讨论 628:今天是冲刺的最后一天了,好紧张好激动,我们组还有好些个功能没实现呢,感觉崩崩哒!还要实现选择不同的专业导入不同的课表呢. 601:在修改教师帐号信息的页面要有当密码格式.用户名格式等 ...
- oracle 的安装脚本
==[root@oracle ~]# cat 1.preusers.sh ==#!/bin/bash#Purpose:Create 3 groups named 'oinstall','dba','o ...
- 【CodeForces 504A】Misha and Forest
题 题意 有n个点,代号分别为0到n-1,然后这n个点有d个相连点,相连点的XOR sum 为s,求所有的边. 分析 知识:a^b^a=b,a^b^b=a. 把相连点为1的i存进队列,i的唯一相连点就 ...
- android 6.0 SDK中删除HttpClient的相关类的解决方法
一.出现的情况 在eclipse或 android studio开发, 设置android SDK的编译版本为23时,且使用了httpClient相关类的库项目:如android-async-http ...
- 11.Android之常用对话框AlertDialog学习
(1)首先我们写个简单的AlertDialog对话框,要创建一个AlertDialog,就要用到AlertDialog.Builder中的create()方法,然后创建对话框可以设置对话框的属性,比如 ...
- BZOJ-1196 公路修建问题 最小生成树Kruskal+(二分??)
题目中一句话,最大费用最小,这么明显的二分的提示(by 以前morestep学长的经验传授)...但完全没二分,1A后感觉很虚.. 1196: [HNOI2006]公路修建问题 Time Limit: ...
- GridView动态添加列之后,导致PostBack(回发)页面数据丢失问题解决
直入主题,首先声明,这个问题是无法解决的,特此在这说明 一.如何动态添加列,如下: 在页面重写OnInit事件,至于为什么要在这个事件写,根据页面的声明周期和经验可知(不用去别的地方找了,这个我找了之 ...