求回文子串 O(n) manacher算法
leetcode之最长回文子串Golang(马拉车算法Manacher‘sAlgorithm)

leetcode之最长回⽂⼦串Golang(马拉车算法Manacher‘sAlgorithm)最开始尝试的是暴⼒破解的⽅法求最长的回⽂⼦串,可是总是超时,因为暴⼒破解⽅法的时间复杂度是O(N3)然后去查了⼀下求回⽂⼦串的⽅法,发现了这个马拉车算法(Manacher's Algorithm)马拉车算法求最长的回⽂⼦串的时间复杂度是O(N)奇数化字符串回⽂字符串有奇偶两种形式:a aba bb abba所以为了避免我们在算法中对奇偶两种形式分开讨论,我们就将这两种形式都转化为奇数形式(也就是回⽂串的总的字符数是奇数个),⽅法就是在每个字符之间的空隙之间加⼊特殊字符,例如: a ==> #a#aba ==> #a#b#a#bb ==> #b#b#因为字符数加上他们之间的空隙数的总数总是为奇数(空隙数⽐字符数多⼀个,两个相邻的数字加起来和为奇数),所以通过这种⽅式,我们可以将字符串处理为总数为奇数回⽂串的半径通过上⾯我们的处理以后,我们在定义回⽂串的半径,也就是回⽂串长度的⼀半#a# ==> 半径为2#a#b#a# ==> 半径为4半径就是奇数回⽂串的长度加1,然后除以2得到的结果通过我们处理以后的回⽂串的半径,我们能够很容易的得到原来回⽂串的长度,原来回⽂串的长度就是处理后的回⽂串的半径减1,例如:#a# ==> 半径为2 ==> 原来回⽂串的长度为2-1=1#a#b#a# ==> 半径为4 ==> 原来回⽂串的长度为4-1=3#b#b# ==> 半径为3 ==> 原来回⽂串的长度为3-1=2有了回⽂串的长度,我们只需要知道在最开始的字符串中那个回⽂⼦字符串的起始的位置,我们就能够轻松求得回⽂⼦串,如图所⽰,第⼀⾏表⽰字符串中每个元素的下标,第⼆⾏是处理后的字符串,第三⾏表⽰以当前字符为中⼼的回⽂串的半径(例如以下标为1的b为中⼼的回⽂串是#b#,那么他的半径就是2)原始的字符串babb,对于原来是偶数的回⽂⼦串bb,如图所⽰,下标为6的#就是这个回⽂⼦串的中⼼,⽤下标6减去对应的半径3,结果为3,刚好是回⽂⼦串bb在原始字符串babb中的位置但是如果是原来是奇数的回⽂串,例如bab,那么中⼼字符在图中对应的就是下标为3的值,它的半径为4,如果相减就得到-1,显然这个-1不会是任何⼦串在原始字符串中起始位置。
马拉车算法(manacher)

马拉车算法(manacher)前⾔马拉车算法是⼀个能在线性时间能求出最长回⽂串长度的算法。
个⼈感觉和kmp也许有异曲同⼯之妙。
⾸先,我们的⽬标是求出最长回⽂⼦串的长度。
暴⼒相信⼤家都会,先是枚举L和R然后O(n)判断每个⼦串是否是回⽂串,总时间复杂度O(n3)。
稍微优化⼀下,枚举中间点mid,贪⼼地向两端扩展找到最长的len[i],即以i为回⽂串中⼼的最⼤扩展长度。
当然,我们发现这样只能处理回⽂串长度为奇数的情况。
所以对于这个暴⼒,我们有了⼀个新思路:在两个相邻字符之间插⼊⼀个不会出现的字符。
⽐如aabba,我们把它变成#a#a#b#b#a#,这样每个点向两边扩展出的就⼀定是奇数长度的len[i]了。
于是这个暴⼒的时间复杂度就是O(n2)。
下⾯所使⽤的字符串也是经过了这样的变换的。
这也正打开了马拉车算法的⼤门。
众所周知,对暴⼒算法的优化是对重复计算的优化以及已知信息的利⽤。
先来看重复的计算。
在上⾯O(n2)的算法中,我们对很多⼦串进⾏了重复搜索,最明显的就是,假设i和j都在同⼀个回⽂⼦串内并且关于中⼼对称,那么len[j]>=len[i]。
这是显然的。
于是我们考虑设置⼀个变量mid,表⽰上⾯那个回⽂⼦串的中⼼,mr(maxright)表⽰这个回⽂⼦串的右端点。
哦对了,这个回⽂⼦串是当前右端点最⼤的回⽂⼦串,所以叫做maxright。
当我们⽬前遍历到的i是⼩于mr的时候,即下图这种情况。
如图,我们找到i的对称点i',发现可以⽤ 2*mid-i得到,于是 len[i]=len[mid*2-i],但是这个对称只在我们以mid为重⼼的回⽂串中可以⽤,所以 i+len[i]<=mr,len[i]<=mr-i,综合,len[i]=min(len[mid*2-i],mr-i)。
接下来是另⼀种情况,也就是i在mr的右边,i>=mr。
此时,我们没有多余的信息可以利⽤了,只能以i为重⼼开始和上⾯说的⼀样暴⼒地拓展找len[i],那么此时我们的i就是新的mid,找到的i+len[i]就是新的mr。
左程云最长回文子串

左程云最长回文子串回文字符串是指正序和倒序读起来都一样的字符串。
例如,"level"和"radar"都是回文字符串。
回文字符串有很多应用场景,其中最长回文子串是非常重要的一种。
左程云在他的著作《算法面试通关宝典》中介绍了一种用Manacher 算法求解最长回文子串的方法。
这个算法的时间复杂度为O(n),并且求出的最长回文子串的长度比暴力算法更准确。
Manacher算法的核心思想是将字符串可视化成一个由字符和分隔符组成的新字符串。
为了使得字符串能够处理所有情况,我们需要在字符串的开头和结尾加上两个不同的分隔符。
这个过程可以用以下代码实现:```string preProcess(string s) {int n = s.length();string ret = "^";for (int i = 0; i < n; i++)ret += "#" + s.substr(i, 1);ret += "#$";return ret;}```经过这个处理以后,字符串就有了如下的新形态:^#a#b#b#a#$0123456789我们可以从左到右遍历字符串,记录每个位置对应的最长回文半径p[i]。
在遍历的过程中,我们不断更新最右边的回文右边界R和当前的回文中心C,同时利用之前遍历到的位置的信息来初始化p数组的值。
具体的实现过程可以参考以下代码:```string longestPalindrome(string s) {string T = preProcess(s);int n = T.length(), C = 0, R = 0;int *P = new int[n];memset(P, 0, sizeof(int)*n);for (int i = 1; i < n-1; i++) {int i_mirror = 2*C-i; //相对于C的位置if (R > i) P[i] = min(R-i, P[i_mirror]);else P[i] = 0;while (T[i+1+P[i]] == T[i-1-P[i]]) P[i]++;if (i+P[i] > R) {C = i;R = i + P[i];}}int maxLength = 0, centerIndex = 0;for (int i = 1; i < n-1; i++) {if (P[i] > maxLength) {maxLength = P[i];centerIndex = i;}}delete[] P;return s.substr((centerIndex - maxLength - 1) / 2, maxLength); }```在上述代码中,我们通过比较记录的最长回文半径和当前回文右边界和当前位置的距离,来寻找最长的回文半径,并更新回文中心和回文右边界的位置。
Manacher算法详解

Manacher算法详解ManacherManacher算法是⼀个⽤来查找⼀个字符串中的最长回⽂⼦串(不是最长回⽂序列)的线性算法。
它的优点就是把时间复杂度为O(n2)的暴⼒算法优化到了O(n)。
⾸先先让我们来看看最原始的暴⼒扩展,分析其存在的弊端,以此来更好的理解Manacher算法。
暴⼒匹配暴⼒匹配算法的原理很简单,就是从原字符串的⾸部开始,依次向尾部进⾏遍历,每访问⼀个字符,就以此字符为中⼼向两边扩展,记录该点的最长回⽂长度。
那么我们可以想想,这样做存在什么弊端,是不是可以求出真正的最长回⽂⼦串?答案是显然不⾏的,我们从两个⾓度来分析这个算法1.不适⽤于偶数回⽂串我们举两个字符串做例⼦,它们分别是 "aba","abba",我们通过⾁眼可以观察出,它们对应的最长回⽂⼦串长度分别是3和4,然⽽我们要是⽤暴⼒匹配的⽅法去对这两个字符串进⾏操作就会发现,"aba" 对应的最长回⽂长是 "131","abba" 对应的最长回⽂长度是 "1111",我们对奇数回⽂串求出了正确答案,但是在偶数回⽂串上并没有得到我们想要的结果,通过多次测试我们发现,这种暴⼒匹配的⽅法不适⽤于偶数回⽂串2.时间复杂度O(n2)这⾥的时间复杂度是⼀个平均时间复杂度,并不代表每⼀个字符串都是这个复杂度,但因为每到⼀个新位置就需要向两边扩展⽐对,所以平均下来时间复杂度达到了O(n*n)。
Manacher算法本质上也是基于暴⼒匹配的⽅法,只不过做了⼀点简单的预处理,且在扩展时提供了加速Manacher对字符串的预处理我们知道暴⼒匹配是⽆法解决偶数回⽂串的,可Manacher算法也是⼀种基于暴⼒匹配的算法,那它是怎么来实现暴⼒匹配且⼜不出错的呢?它⽤来应对偶数字符串的⽅法就是——做出预处理,这个预处理可以巧妙的让所有字符串都变为奇数回⽂串,不论它原本是什么。
ManachersAlgorithm马拉车算法

ManachersAlgorithm马拉车算法这个马拉车算法 Manacher‘s Algorithm 是⽤来查找⼀个字符串的的线性⽅法,由⼀个叫 Manacher 的⼈在 1975 年发明的,这个⽅法的最⼤贡献是在于将时间复杂度提升到了线性,这是⾮常了不起的。
对于回⽂串想必⼤家都不陌⽣,就是正读反读都⼀样的字符串,⽐如 "bob", "level", "noon" 等等,那么如何在⼀个字符串中找出最长回⽂⼦串呢,可以以每⼀个字符为中⼼,向两边寻找回⽂⼦串,在遍历完整个数组后,就可以找到最长的回⽂⼦串。
但是这个⽅法的时间复杂度为 O(n*n),并不是很⾼效,下⾯我们来看时间复杂度为 O(n)的马拉车算法。
由于回⽂串的长度可奇可偶,⽐如 "bob" 是奇数形式的回⽂,"noon" 就是偶数形式的回⽂,马拉车算法的第⼀步是预处理,做法是在每⼀个字符的左右都加上⼀个特殊字符,⽐如加上 '#',那么bob --> #b#o#b#noon --> #n#o#o#n#这样做的好处是不论原字符串是奇数还是偶数个,处理之后得到的字符串的个数都是奇数个,这样就不⽤分情况讨论了,⽽可以⼀起搞定。
接下来我们还需要和处理后的字符串t等长的数组p,其中 p[i] 表⽰以 t[i] 字符为中⼼的回⽂⼦串的半径,若 p[i] = 1,则该回⽂⼦串就是 t[i] 本⾝,那么我们来看⼀个简单的例⼦:# 1 # 2 # 2 # 1 # 2 # 2 #1 2 1 2 5 2 1 6 1 2 3 2 1为啥我们关⼼回⽂⼦串的半径呢?看上⾯那个例⼦,以中间的 '1' 为中⼼的回⽂⼦串 "#2#2#1#2#2#" 的半径是6,⽽未添加#号的回⽂⼦串为"22122",长度是5,为半径减1。
回文字符串

p[i] = min(p[2*id - i], maxid - i);
}
else{
p[i] = 1;
}
while (newstr[i+p[i]] == newstr[i-p[i]])
p[i]++;
if (p[i] + i > maxid){
maxid = p[i] + i;
先把核心代码贴上:
for (i = 0; i < len; i++){
if (maxid > i){
p[i] = min(p[2*id - i], maxid - i);
}
else{
p[i] = 1;
}
while (newstr[i+p[i]] == newstr[i-p[i]])
p[i]++;
if (p[i] + i > maxid){
using namespace std;
const int MAX = 100001;
int len, p[2*MAX];
char str[2*MAX], newstr[2*MAX];
void change()
{
int i;
newstr[0] = '@';
newstr[1] = '#';
for (i = 0; i < len; i++){
maxid = p[i] + i;
id = i;
}
if (ans < p[i])
ans = p[i];
}
Manacher算法(马拉车算法)浅谈

Manacher算法(马拉车算法)浅谈什么是Manacher算法?转载⾃百度百科Manachar算法主要是处理字符串中关于回⽂串的问题的,它可以在 O(n)的时间处理出以字符串中每⼀个字符为中⼼的回⽂串半径,由于将原字符串处理成两倍长度的新串,在每两个字符之间加⼊⼀个特定的特殊字符,因此原本长度为偶数的回⽂串就成了以中间特殊字符为中⼼的奇数长度的回⽂串了。
Manacher算法提供了⼀种巧妙的办法,将长度为奇数的回⽂串和长度为偶数的回⽂串⼀起考虑,具体做法是,在原字符串的每个相邻两个字符中间插⼊⼀个分隔符,同时在⾸尾也要添加⼀个分隔符,分隔符的要求是不在原串中出现,⼀般情况下可以⽤#号。
⼀句话概括就是求解关于回⽂字串的⼀个线性⽅法如何实现Manacher算法?字符串改装函数trans①使原字符串转换为奇数串我们可以发现回⽂串有以下特征①奇数串②偶数串其中不管奇数串还是偶数串只要每个字符后⾯加⼀个字符,再在开头加⼀个字符例如#会强制把他们都转换为奇数串例如bob->#b#o#b# dood->#d#o#o#d#并且他们的回⽂特性不变并且可以发现⼀个规律那就是回⽂半径-1就是最⼤字串的回⽂长度,例如#b#o#b#的最⼤回⽂字串长度为4-1=3,#d#o#o#d#的最⼤回⽂长度为5-1=4②确定回⽂起始位置的转换我们还可以发现这样⼀个规律,如果开头再添加⼀个字符(应该为⾮#)例如$ bob->\(#b#o#b# dood->\)#d#o#o#d# 那么他的回⽂起始位置就在(原来带#的字符串的中⼼的数组下标-原来的回⽂半径)/2例如$#b#o#b#的回⽂中⼼在(4-4)/2=0$#d#o#o#d#=(5-5)/2=0函数完整代码string trans(string a){string t="$#";//初始化开头for(int i=0;i<a.size();i++)//每个后⾯加⼀个#{t+=a[i];t+='#';}return t;//返回}回⽂半径数组p[i]的计算完全是下⾯这⼀⾏的精髓p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;⽬前还对这⼀⾏并不是很理解⽆法说明,但是可以记住这是⼀个规律完整代码(洛⾕模板题 P3805 【模板】manacher算法)#include <bits/stdc++.h>using namespace std;string trans(string a){string t="$#";//初始化开头for(int i=0;i<a.size();i++)//每个后⾯加⼀个#{t+=a[i];t+='#';}return t;//返回}int p[110000050];//开⼤⼀点开⼩了还reint main(){ios::sync_with_stdio(0);//关闭同步三步⾛cin.tie(0);cout.tie(0);string a,b;//输⼊原字符串cin>>a;b=a;a=trans(a);//转换int mx,id,rl,rc,ans=-1;for(int i=1;i<a.size();i++){p[i]=mx>i?min(p[2*id-i],mx-i):1;//求p[i]的初始值while(a[i+p[i]]==a[i-p[i]])//如果回⽂半径延伸可以的话,p[i]++p[i]++;if(mx<i+p[i])//如果具有回⽂性质的最右边界⼩于⽬前更新的回⽂半径 {mx=i+p[i];//更新右边界id=i;//更新回⽂⼦串的初始位置}ans=max(ans,p[i]-1);//寻找最⼤值}cout<<ans;/*输出回⽂⼦串cout<<"\n";for(int i=(id-ans)/2;i<ans;i++)cout<<b[i];cout<<"\n";*/}。
马拉车算法详解

马拉车算法详解简述 Manacher算法,⼜称马拉车算法,它是⽤于求⼀个字符串的最长回⽂⼦串长度的算法,时间和空间复杂度为O(n)。
算法思想 求⼀个字符串的最长回⽂⼦串长度,我们如果⽤暴⼒来做,我们就要取出这个串的所有⼦串,然后判断这个⼦串是不是回⽂串,复杂度是n⽅的。
那么马拉车为何如此神奇能做到O(n)呢? ⾸先我们来看这两个串:abba和abcba。
第⼀个串的回⽂中⼼在两个b之间,第⼆个串的回⽂中⼼为字符c,这样两种情况我们分类讨论太⿇烦了,所以我们考虑对原字符串进⾏⼀个字符填充,abba->@#a#b#b#a#,abcba->@#a#b#c#b#a#,这样他们都变成了奇数,讨论的情况就⼀致了。
设原字符串长度为len1,重构后长度为len2,则len2=len1*2+2且对于s[i],它在重构后字符串的对应下标为i*2+2。
由此我们可以写出重定义字符串的代码: ⾸先我们规定⼏个变量的含义:len[i]为以i为中⼼点的最长回⽂序列的回⽂半径,po为当前中⼼点,p为当前回⽂⼦串的右边界。
我们可以得出,ans=len[i]-1,因为加⼊填充字符后的str的最长回⽂串为2*len[i]-1,⼜因为#号的个数⽐原字符多⼀个,所以#字符的总数为len[i]个,故原字符的最长回⽂⼦串为len[i]-1个。
当i<=p时,找到i相对于po的对称点j,如果len[j]<p-i,如下图: 由对称性得知,i和j左右的字符是⼀样的,所以len[i]=len[j]。
如果len[j]>=p-i,由对称性,说明以i为中⼼的回⽂串会扩散⾄p以外,⽽⼤于p部分的串我们还没⽤匹配,所以我们⼀个⼀个匹配,更新po和len[i]。
int getstr(){//重定义字符串,s 为原字符串,str 为新串,len 为s 串长度,返回新串长度int k=0,len=strlen(s);str[k ++]='@';for (int i=0;i<len;i++){str[k ++]='#';str[k ++]=s[i];}str[k ++]='#';return k;} 当i>p时,说明以i为中⼼的回⽂串⼀点都没匹配,就只能⽼⽼实实⼀个⼀个匹配了。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
求回文子串O(n) manacher算法
回文串定义:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
回文子串,顾名思义,即字符串中满足回文性质的子串。
经常有一些题目围绕回文子串进行讨论,比如HDOJ_3068_最长回文,求最长回文子串的长度。
朴素算法是依次以每一个字符为中心向两侧进行扩展,显然这个复杂度是O(N^2)的,关于字符串的题目常用的算法有KMP、后缀数组、AC自动机,这道题目利用扩展KMP 可以解答,其时间复杂度也很快O(N*logN)。
但是,今天笔者介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher算法。
大家都知道,求回文串时需要判断其奇偶性,也就是求aba和abba的算法略有差距。
然而,这个算法做了一个简单的处理,很巧妙地把奇数长度回文串与偶数长度回文串统一考虑,也就是在每个相邻的字符之间插入一个分隔符,串的首尾也要加,当然这个分隔符不能再原串中出现,一般可以用‘#’或者‘$’等字符。
例如:
原串:abaab
新串:#a#b#a#a#b#
这样一来,原来的奇数长度回文串还是奇数长度,偶数长度的也变成以‘#’为中心的奇数回文串了。
接下来就是算法的中心思想,用一个辅助数组P记录以每个字符为中心的最长回文半径,也就是P[i]记录以Str[i]字符为中心的最长回文串半径。
P[i]最小为1,此时回文串为Str[i]本身。
我们可以对上述例子写出其P数组,如下
新串:# a # b # a # a # b #
P[] : 1 2 1 4 1 2 5 2 1 2 1
我们可以证明P[i]-1就是以Str[i]为中心的回文串在原串当中的长度。
证明:
1、显然L=2*P[i]-1即为新串中以Str[i]为中心最长回文串长度。
2、以Str[i]为中心的回文串一定是以#开头和结尾的,例如“#b#b#”或“#b#a#b#”所以L减去最前或者最后的‘#’字符就是原串中长度的二倍,即原串长度为(L-1)/2,化简的P[i]-1。
得证。
依次从前往后求得P数组就可以了,这里用到了DP(动态规划)的思想,也就是求P[i]的时候,前面的P[]值已经得到了,我们利用回文串的特殊性质可以进行一个大大的优化。
我先把核心代码贴上:
for(i=1;i<n;i++)
{
if(MaxId>i)
{
p[i]=Min(p[2*id-i],MaxId-i);
}
else
{
p[i]=1;
}
while(a[i+p[i]]==a[i-p[i]])
{
p[i]++;
}
if(p[i]+i>MaxId)
{
MaxId=p[i]+i;
id=i;
}
}
为了防止求P[i]向两边扩展时可能数组越界,我们需要在数组最前面和最后面加一个特殊字符,令P[0]=‘$’最后位置默认为‘\0’不需要特殊处理。
此外,我们用MaxId变量记录在求i之前的回文串中,延伸至最右端的位置,同时用id记录取这个MaxId的id值。
通过下面这句话,算法避免了很多没必要的重复匹配。
if(MaxId>i)
{
p[i]=Min(p[2*id-i],MaxId-i);
}
那么这句话是怎么得来的呢,其实就是利用了回文串的对称性,如下图,
j=2*id-i即为i关于id的对称点,根据对称性,P[j]的回文串也是可以对称到i这边的,但是如果P[j]的回文串对称过来以后超过MaxId的话,超出部分就不能对称过来了,如下图,所以这里P[i]为的下限为两者中的较小者,p[i]=Min(p[2*id-i],MaxId-i)。
算法的有效比较次数为MaxId次,所以说这个算法的时间复杂度为O(n)。
附HDOJ_3068_最长回文代码:
#include <stdio.h>
#define M 110010
char b[M],a[M<<1];
int p[M<<1];
int Min(int a,int b)
{
return a<b?a:b;
}
int main()
{
int i,n,id,MaxL,MaxId;
while(scanf("%s",&b[1])!=EOF)
{
MaxL=MaxId=0;
for(i=1;b[i]!='\0';i++)
{
a[(i<<1)]=b[i];
a[(i<<1)+1]='#';
}
a[0]='?';a[1]='#';
n=(i<<1)+2;a[n]=0;
MaxId=MaxL=0;
for(i=1;i<n;i++)
{
if(MaxId>i)
{
p[i]=Min(p[2*id-i],MaxId-i); }
else
{
p[i]=1;
}
while(a[i+p[i]]==a[i-p[i]])
{
p[i]++;
}
if(p[i]+i>MaxId)
{
MaxId=p[i]+i;
id=i;
}
if(p[i]>MaxL)
{
MaxL=p[i];
}
}
printf("%d\n",MaxL-1);
}
return0;
}。