您的位置:首页 > 其它

最长回文子串(Longest Palindromic Substring)

2013-07-05 22:30 239 查看

最长回文子串(Longest Palindromic Substring)

一个「对称」的序列,就可称为回文序列,譬如:aba,abba 等。详细介绍参看: http://zh.wikipedia.org/wiki/%E5%9B%9E%E6%96%87%E6%95%B0

最长回文子串问题是要求在给出的一个序列中,找到最长的回文字串。譬如:一个序列 cabccba,它的最长回文子串是 abccba。

暴力

暴力穷举可以解决问题。三个循环穷举所有可能的序列。

?
但算法的复杂度是 O(n^3)。

一个更好的思路

在面试的是被问到这个题目,我拙计,下面是当天聊天中给出的思路:


2013-06-10

捣乱 9:52:08

开始想到遍历,不放过任何一个回文中心,计算最大回文串,但欠妥,效率低。

回文串的难处在回文串中有回文串。

另一个是用栈,描述一下:

用一个栈,不断往里压串种的元素,如果发现回文串,不压栈而且要弹出栈里的元素,并相应的记录回文串的位置和大小(可用一个数组来存储)

当发现「回文串 1 中存在回文串 2,回文串 3,回文串 4,...回文串 N」的情况,要作检测,具体是看回文串 2 和回文串 N ,回文串 3 和回文串 N-1 是否对称且相等。譬如:

d abc cba abc cba d

是回文串中有回文串的情况。

abc 和 cba 是回文串,接下来的又是一样。所以后来栈是这样:d,只剩一个元素,并且还有一个 d 准备压栈,但压栈的时候发现:因为 dd 是回文又之前处理出现了回文,因此要检测 dd 范围内的回文是否对称且相等。

这种方法有穷举的嫌疑,但规避了很多不合题意的情况。


上面的做法不稳定,因为其中的回溯过程(加粗部分)。

Manacher 线性算法

利用一个辅助数组 arr
,其中 arr[i] 记录的是以 str[i] 为中心的回文子串长度。当计算 arr[i] 的时候,arr[0...i-1] 是已知并且可被利用的。Manacher 核心在于:用 mx 记录之前计算的最长的回文子串长度所能到达的最后边界,用 id 记录其对应的中心,可以利用回文子串中的回文子串信息。



假设 id 与 mx 已经得出,当计算以 str[i] 为中心回文子串长度时,因为已经可以确定绿色部分已经是回文子串了,所以可以利用以 str[j] 为中心回文子串长度即 arr[j]。在上图的情况下,所以可以从箭头所指出开始比较。还有一种情况:





这种情况下,不能直接利用以 str[j] 为中心回文子串长度即 arr[j],因为以 id 为中心回文子串长度只计算到了绿色箭头所指之处,所以能力利用的信息是 mx-i,比较 mx-i 之后的字符。

下面个举一例:

0123456789

ceabadabac

1112141?

当计算「?」即 arr[7] 的时候,id = 5,mx = 8,所以 arr[7] 可以给一个初值为 arr[2*id-7=3]=2,并且比较 str[7-2] 与 str[7+2] 是否相等......

0123456789

cdabadabac

1113141?

当计算「?」即 arr[7] 的时候,id = 5,mx = 8,此时 arr[7] 不能赋 arr[2*id-7=3]=3 的初值,因为以 id 为中心的回文子串只为图中蓝色部分:

。所以,arr[7] 只能赋值为 mx-i = 8-7 = 1,继续比较以更新 arr[7]。

Manacher 线性算法只要在纸上演算一遍就明白了。

从上面的描述,Manacher 算法只扫描了一遍,在具体计算中,借用了历史数据以更快的速度算出当下的结果,从而避免重复的比较,因此是线性的?!(笔者还无法证明)

下面是 Python 的算法描述:

?
#a#b#c#d#c#b#a#

[1, 2, 1, 2, 1, 2, 1, 8, 1, 2, 1, 2, 1, 2, 1]

一个小 trick 是在原串中穿插字符「#」,可以将统一奇数回文串和偶数回文串的情况。

捣乱 2013-06-30
http://daoluan.net
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: