原文链接:英文版链接

  首先,我们将字符串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)算法的更多相关文章

  1. Leetcode 5. Longest Palindromic Substring(最长回文子串, Manacher算法)

    Leetcode 5. Longest Palindromic Substring(最长回文子串, Manacher算法) Given a string s, find the longest pal ...

  2. 最长回文子串的Manacher算法

    对于一个比较长的字符串,O(n^2)的时间复杂度是难以接受的.Can we do better? 先来看看解法2存在的缺陷. 1) 由于回文串长度的奇偶性造成了不同性质的对称轴位置,解法2要对两种情况 ...

  3. 51nod1089(最长回文子串之manacher算法)

    题目链接: https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1089 题意:中文题诶~ 思路: 我前面做的那道回文子串的题 ...

  4. 求最长回文子串:Manacher算法

    主要学习自:http://articles.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html 问题描述:回文字符串就是左右 ...

  5. 最长回文子串(Manacher算法)

    回文字符串,想必大家不会不熟悉吧? 回文串会求的吧?暴力一遍O(n^2)很简单,但当字符长度很长时便会TLE,简单,hash+二分搞定,其复杂度约为O(nlogn), 而Manacher算法能够在线性 ...

  6. 计算字符串的最长回文子串 :Manacher算法介绍

    转自: http://www.open-open.com/lib/view/open1419150233417.html Manacher算法 在介绍算法之前,首先介绍一下什么是回文串,所谓回文串,简 ...

  7. 51Nod 1089 最长回文子串 V2 —— Manacher算法

    题目链接:https://vjudge.net/problem/51Nod-1089 1089 最长回文子串 V2(Manacher算法) 基准时间限制:1 秒 空间限制:131072 KB 分值:  ...

  8. 51 Nod 1089 最长回文子串(Manacher算法)

    1089 最长回文子串 V2(Manacher算法)  基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 回文串是指aba.abba.cccbccc.aaa ...

  9. 最长回文子串 —— Manacher (马拉车) 算法

    最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...

随机推荐

  1. Ubuntu下vim的配置

    由于刚开始学习Linux,对佷多操作还不熟悉,遇到了一些困难,在这里记录一下. 1  安装vim.俺我现在的理解,vim有控制台版本和GUI版本.控制台版本貌似是Ubuntu自带的,可以在终端输入 v ...

  2. UItableView的编辑--删除移动cell

    // // RootViewController.m // UI__TableView的编辑 // // Created by dllo on 16/3/17. // Copyright © 2016 ...

  3. Python学习日记

    江林楠学习了一下午后给大家呈现的20分钟速成Python—— 一些基本的语法:1.python无变量声明 直接a = []即可.2.python为对齐语言,用制表符表示语句块的嵌套.3.python语 ...

  4. iOS开发中的错误整理,重写的构造函数中,没有通过self调用

  5. Java-TreeSet

    如下: package 集合类.Set类; /** * Set不允许重复数据 */ /** * TreeSet 是用来进行集合排序的,请注意他和LinkedHashSet的区别. TreeSet是按照 ...

  6. sql-where

    查询表时不一定每一次都要将表格内的资料都完全抓出.在许多时候,我们会需要选择性地抓资料.就我们的例子来说,我们可能只要抓出营业额超过 $1,000 的资料. 要做到这一点,需要用到 WHERE 这个指 ...

  7. 替换文件最后一行中的所有e 为 E

    #root@athena5plus:~# cat b    northwest       NW     Charles Main           3.0      .98      3      ...

  8. 淘宝中的UV,PV,IPV

    1.  UV & PV UV: 店铺各页面的访问人数,一个用户在一天内多次访问店铺被记为一个访客(去重) ; Unique visitors PV: 店铺内所有页面的浏览总量(次数累加); p ...

  9. 洛谷P1203 [USACO1.1]坏掉的项链Broken Necklace

    题目描述 你有一条由N个红色的,白色的,或蓝色的珠子组成的项链(3<=N<=350),珠子是随意安排的. 这里是 n=29 的二个例子: 第一和第二个珠子在图片中已经被作记号. 图片 A ...

  10. hashcode与字符串

    问题1. 不同的字符串可能会有相同的HashCode吗? hashcode是用来判断两个字符串是否相等的依据,不同的字符串不可能有相同的hashcode,但不同的hashCode经过与长度的取余,就很 ...