manacher算法求最长回文子串(Longest Palindromic Substring)

来源:互联网 发布:周易掐指算法 编辑:程序博客网 时间:2024/06/07 18:02

经典的快速求最长回文子串的算法是manacher算法(俗称“马拉车”),时间复杂度o(n),感觉能在o(n)时间内解决问题的算法都是神啊。不过这个算法用处比较单一,思想也不具有普遍性。回文这个东西,本来用的就不多,看看理解一下就好。

推两篇博文:博文一,博文二。一是英文,二是中文,看完之后就应该能理解的差不多了,其中博文二在博文一的参考文献里,外国人居然也看中文博客,惊奇!

下面自己对一些比较难理解的地方注释一下,作个记录便于以后翻看,以博文一来说明!

1.首先,要将字符串处理一下,中间加入一个与字符串所有字符都不相同的字符,比如'#',这样字符串肯定是奇数个了,方便处理。至于前后又加了'^'和‘$‘,都是方便计算的,下标惹的祸。这就要注意下标起始1...n-1。

2.文中的p[i]的值,也就是第i个字符的最长回文值,博文一是不加第i字符本身的,从后面的过程来看,这种做法比较合理清晰,中文博客里喜欢加上,不过不影响整个算法流程。


3.这个算法的思想就是:比如你前面的字符回文串求出来了,后面要求的字符回文串就可以利用前面的结果。这个利用是有前题的,(以博文一的上图为例)就是前面有个字符i=11,它的回文串很长,这么长的回文串是花了时间代价一点点比较出来的,在计算时从自身开始一直向两边比较到了[L, R],那么这里有个很重要的信息就是[2...10]和[12...20]这两个区间的字符串是对称相等的。在求i=[12...19]的时候,我是不是可以利用一下区间[2...10]的结果呢,因为它们的回文串早已经求出来了。其实眼看可以看出,比如i=13的字符b,它对称的就是前面的i=9字符b,因为i=9字符b的串长是1,所以i=13字符b也是1,一直这样算下去,一直到i=19,貌似问题就解决了,其实不然,当i=15时,对称的i=7,也就是图中的i’,它的最大串长值是7。这个值超出了以i’为中心的i=[2...11],或者说以i=15为中心的[11...20]的范围。问题来了,你没超范围时,因为前后区间对称,可以直接赋值;现在超了,就是说右侧R处后面的都还没比较呢,当然不知道是否相等了,那只有老实儿地比较吧。那我是不是要从i=15开始分别向两端比较呢,不用,区间[11...20]内的字符,以i=15为中心的那些已经是对称的了,因为前面i’的值7已经保证了啊,你对称长度20-15=5,人家是7唉。所以,对i=15来说,求回文串分别从i=20开始向右、从对称的左边i=15-(20-15)开始向左开始逐渐向两端比较就行。有人就想,要不是这里i=11的右端R限制,因为i’的值是7,还可以有多几个相等的呢。嘿嘿,所以R-i和P[i’]=7的大小还决定了不同的情况。

    要保存这个R值,它是已求所有P[i]值中最右的边界,这里要注意,并不一定是已求值中最大的P[i]值就是最右的边界,虽然这里在求i=15时,R=20这个边界确实是P[11]=9的右边界,但再往后计算时,肯定是别的数的右边界。

    还一种情况,求i=21时,假设,啊我们假设i=21之前求的最右边界就是R(事实上,这里确实是R),那没办法,从i=21开始,分别向两端比较吧。没可利用的信息了。

    说了一堆,对应博文一的代码就:用一个数组P保存求得的各个字符的最大回文长度值;右边界R,这个每计算一个回文值时就要判断一下,如果有更右的边界,替换,对应代码里的if (i + P[i] > R)语句;求一个新的字符i的回文串长度值,看R和字符i的关系,来确定从何处开始匹配,也就是给P[i]一个初始值,如果i在边界R或右边时(i>=R),只能重新匹配,P[i]=0;在边界左侧(i<R)时,还要看i对应的i’的P[i’]值,R-i和P[i’]取较小值,对应min(R - i, P[i_mirror])。

std::string preProcess(std::string s) {int n = s.length();if (n == 0) return "^$";std::string ret = "^";for (int i = 0; i < n; i++)ret += "#" + s.substr(i, 1);ret += "#$";return ret;}std::string longestPalindrome(std::string s) {std::string T = preProcess(s);int n = T.length();int *P = new int[n];int C = 0, R = 0;for (int i = 1; i < n - 1; i++) {int i_mirror = 2 * C - i; // equals to i' = C - (i-C)P[i] = (R > i) ? min(R - i, P[i_mirror]) : 0;// Attempt to expand palindrome centered at iwhile (T[i + 1 + P[i]] == T[i - 1 - P[i]])P[i]++;// If palindrome centered at i expand past R,// adjust center based on expanded palindrome.if (i + P[i] > R) {C = i;R = i + P[i];}}// Find the maximum element in P.int maxLen = 0;int centerIndex = 0;for (int i = 1; i < n - 1; i++) {if (P[i] > maxLen) {maxLen = P[i];centerIndex = i;}}delete[] P;return s.substr((centerIndex - 1 - maxLen) / 2, maxLen);}



0 0