manacher算法求最长回文子序列
Posted 爱国呐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了manacher算法求最长回文子序列相关的知识,希望对你有一定的参考价值。
一:背景
给定一个字符串,求出其最长回文子串。例如:
- s="abcd",最长回文长度为 1;
- s="ababa",最长回文长度为 5;
- s="abccb",最长回文长度为 4,即bccb。
以上问题的传统思路大概是,遍历每一个字符,以该字符为中心向两边查找。其时间复杂度为O(n^2),效率很差。
1975年,一个叫Manacher的人发明了一个算法,Manacher算法(中文名:马拉车算法),该算法可以把时间复杂度提升到O(n)。下面来看看马拉车算法是如何工作的。
二:算法过程分析
由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里)。
举个例子:s="abbahopxpo"
,转换为s_new="$#a#b#b#a#h#o#p#x#p#o#"
(这里的字符 $ 只是为了防止越界,下面代码会有说明),如此,s 里起初有一个偶回文abba
和一个奇回文opxpo
,被转换为#a#b#b#a#
和#o#p#x#p#o#
,长度都转换成了奇数。
定义一个辅助数组int p[]
,其中p[i]
表示以 i 为中心的最长回文的半径,例如:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
s_new[i] | $ | # | a | # | b | # | b | # | a | # | h | # | o | # | p | # | x | # | p | # |
p[i] | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 4 | 1 | 2 | 1 |
可以看出,p[i] - 1
正好是原字符串中最长回文串的长度。
接下来的重点就是求解 p 数组,如下图:
设置两个变量,mx 和 id 。mx 代表以 id 为中心的最长回文的右边界,也就是mx = id + p[id]
。
假设我们现在求p[i]
,也就是以 i 为中心的最长回文半径,如果i < mx
,如上图,那么:
if (i < mx) p[i] = min(p[2 * id - i], mx - i);
2 * id - i
为 i 关于 id 的对称点,即上图的 j 点,而p[j]
表示以 j 为中心的最长回文半径,因此我们可以利用p[j]
来加快查找。
三:代码
//指定位置判断回文,此题为指定包含最后一个的最长回文序列。 char ma[maxn*2], s[maxn]; int mp[maxn*2]; int ans,Mlen; void Manacher(char s[],int len) { int l=0; ma[l++]=‘$‘; ma[l++]=‘#‘; for(int i=0; i<len; i++) { ma[l++]=s[i]; ma[l++]=‘#‘; } ma[l]=0; int mx=0,id=0; for(int i=0; i<l; i++) { mp[i]=mx>i?min(mp[2*id-i],mx-i):1; while(ma[i+mp[i]]==ma[i-mp[i]]) mp[i]++; if(i+mp[i]>mx) { mx=i+mp[i]; id=i; } // 这里可以check(ma[i]) ans=max(ans,mp[i]-1); if(mp[i]-1+i==l-1) Mlen=max(Mlen,mp[i]-1); } } int main() { int T; cin>>T; int kcase = 1; while(T--) { memset(ma,0,sizeof(ma)); memset(mp,0,sizeof(mp)); cin>>s; int len=strlen(s); ans=0; Mlen=0; Manacher(s,len); if(ans == len) printf("Case %d: %d\n", kcase++, ans); else printf("Case %d: %d\n", kcase++, len - Mlen + len); } }
四:题目
这个题目就是在原来的基础上添加了一个判断条件,看清楚在哪里添加。
//101350I - 2017 ACM Arabella Collegiate Programming Contest - Mirrored String II #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1010; char ma[maxn*2]; int mp[maxn*2]; char s[maxn]; int check(char zzz) { if(zzz==‘A‘||zzz==‘H‘||zzz==‘I‘||zzz==‘M‘||zzz==‘O‘||zzz==‘#‘|| zzz==‘T‘||zzz==‘U‘||zzz==‘V‘||zzz==‘W‘||zzz==‘X‘||zzz==‘Y‘) return 1; return 0; } void Manacher(char s[],int len) { int l=0; ma[l++]=‘$‘; ma[l++]=‘#‘; for(int i=0; i<len; i++) { ma[l++]=s[i]; ma[l++]=‘#‘; } ma[l]=0; int mx=0,id=0; for(int i=0; i<l; i++) { mp[i]=mx>i?min(mp[2*id-i],mx-i):1; while(check(ma[i+mp[i]])&&ma[i+mp[i]]==ma[i-mp[i]])//在这里添加check { mp[i]++; } if(i+mp[i]>mx) { mx=i+mp[i]; id=i; } } } int main() { int T; cin>>T; while(T--) { cin>>s; int len=strlen(s); Manacher(s,len); int ans=0; for(int i=0; i<len*2+2; i++) if(check(ma[i]))//这里添加check ans=max(ans,mp[i]-1); cout<<ans<<endl; } }
以上是关于manacher算法求最长回文子序列的主要内容,如果未能解决你的问题,请参考以下文章
manacher算法 O(n) 求字符串中最长回文子串 hdu 3068(模板题)