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不会是任何⼦串在原始字符串中起始位置。
马拉松算法(Manachersalgorithm)(未完成)

马拉松算法(Manachersalgorithm)(未完成)马拉松算法:马拉松算法是⽤来计算⼀个字符串中最长的回⽂字符串(对称字符串,如aba abba)。
⾸先,我们拿到⼀个字符串S,然后在S中的每个字符之间加#。
例如:S="abcb" T="a#b#c#b"我们T字符串的每⼀个T[i]向延伸d个字符使得 T[i-d,i+d]是⼀个回⽂字符串。
你会⽴刻发现,d就是以T[i]为中⼼的最长回⽂字符串的长度。
我们建⽴⼀个P数组,是的P数组的长度等于T的长度,每⼀个P[i]的值表⽰对应的T[i]为中⼼的最⼤回⽂字符串的长度。
如下:T = # a # b # a # a # b # a #P = 0103016103010瞅⼀下P,我们⽴刻就能发现最长的回⽂字符串长度是P[6],对应字符串是"abaaba"。
你有没有发现,加⼊#之后以前的奇数回⽂字符串和偶数回⽂字符串都变得优雅了呢(提⽰:这仅仅是为了⽅便讲解,不是算法代码⾥所必须的步骤)。
现在,想象你在回⽂字符串"abaaba"的中间画⼀条线,你会发现P两边的数字也是关于这条线对称的。
不仅仅是这个字符串,你试试"aba"也是⼀样的情况。
这是巧合吗?答说是也是,说不是也不是。
这只是在某种条件下才会出现的,不管怎么样,我们已经取得了很⼤的进步。
让我们来看看更为复杂的字符串"babcbabcbaccba"。
上图展⽰⼀个从P基于T的⽣成过程,假设你已经完成P的⼀部分。
竖实线标记的是回⽂字符串"abcbabcba"的正中间C,两个竖虚线标记回⽂字符串的左边界和右边界。
你在i这个位置,且i位置关于C的镜像位置是i'。
你应该如何能快速计算出P[i]的值呢。
我们看到,i=13,i'=9,我们需要计算的是P[13]。
上图两个绿实线所覆盖的区域关于C对称。
马拉车算法(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。
算法练习题

算法练习题一、基础算法1. 编写一个程序,实现一个冒泡排序算法。
2. 实现一个选择排序算法。
3. 实现一个插入排序算法。
4. 编写一个函数,计算一个整数数组中的最大值和最小值。
5. 编写一个函数,实现二分查找算法。
6. 编写一个函数,实现快速排序算法。
7. 编写一个函数,判断一个整数是否为素数。
8. 编写一个函数,实现反转一个整数数组。
9. 编写一个函数,计算两个整数数组的交集。
10. 编写一个函数,判断一个字符串是否为回文。
二、数据结构11. 实现一个单链表的基本操作,包括插入、删除、查找。
12. 实现一个双向链表的基本操作,包括插入、删除、查找。
13. 实现一个栈的基本操作,包括压栈、出栈、查看栈顶元素。
14. 实现一个队列的基本操作,包括入队、出队、查看队首元素。
15. 实现一个二叉树的基本操作,包括插入、删除、查找。
16. 实现一个二叉搜索树的基本操作,包括插入、删除、查找。
17. 实现一个哈希表的基本操作,包括插入、删除、查找。
三、搜索与图论18. 编写一个程序,实现深度优先搜索(DFS)算法。
19. 编写一个程序,实现广度优先搜索(BFS)算法。
20. 编写一个程序,求解迷宫问题。
21. 编写一个程序,计算一个有向图的拓扑排序。
22. 编写一个程序,计算一个无向图的欧拉回路。
23. 编写一个程序,计算一个加权无向图的最小树(Prim算法)。
24. 编写一个程序,计算一个加权有向图的最短路径(Dijkstra算法)。
25. 编写一个程序,计算一个加权有向图的所有顶点对的最短路径(Floyd算法)。
四、动态规划26. 编写一个程序,实现背包问题。
27. 编写一个程序,计算最长公共子序列(LCS)。
28. 编写一个程序,计算最长递增子序列(LIS)。
29. 编写一个程序,实现编辑距离(Levenshtein距离)。
30. 编写一个程序,实现硬币找零问题。
31. 编写一个程序,实现矩阵链乘问题。
python 最大回文子串算法

python 最大回文子串算法Python最大回文子串算法回文串是指正序和逆序相同的字符串,例如"level"和"noon"都是回文串。
在字符串处理中,求解最大回文子串是一种经典的问题,即找到给定字符串中最长的回文子串。
本文将详细介绍Python中常用的几种最大回文子串算法,包括简单的中心扩展法、动态规划法和马拉车算法。
1. 中心扩展法中心扩展法是最简单直观的求解最大回文子串的方法。
从左到右遍历字符串,以每个字符为中心向两边扩展,找到最长的回文子串。
具体实现如下:def expandCenter(s, left, right):while left >= 0 and right < len(s) and s[left] == s[right]:left -= 1right += 1return right - left - 1def longestPalindrome(s):start, end = 0, 0for i in range(len(s)):len1 = expandCenter(s, i, i) # 以字符为中心扩展len2 = expandCenter(s, i, i + 1) # 以空隙为中心扩展cur_len = max(len1, len2)if cur_len > end - start:start = i - (cur_len - 1) 2end = i + cur_len 2return s[start:end+1]这种方法的时间复杂度是O(n^2),其中n是字符串的长度。
2. 动态规划法动态规划法也是常用的解决最大回文子串问题的方法。
采用二维的动态规划数组dp,其中dp[i][j]表示从第i个字符到第j个字符是否为回文串。
具体实现如下:def longestPalindrome(s):n = len(s)dp = [[False] * n for _ in range(n)]start, end = 0, 0for i in range(n):dp[i][i] = Trueif i < n - 1 and s[i] == s[i + 1]:dp[i][i + 1] = Truestart, end = i, i + 1for i in range(n - 1, -1, -1):for j in range(i + 2, n):if s[i] == s[j] and dp[i + 1][j - 1]:dp[i][j] = Trueif j - i > end - start:start, end = i, jreturn s[start:end + 1]动态规划法的时间复杂度同样为O(n^2),但需要额外的O(n^2)空间来存储dp 数组。
回文的机趣及其应用

回文的机趣及其应用
刘济民
【期刊名称】《阅读与写作》
【年(卷),期】1999(000)002
【摘要】回文有的书称回环,它是变换词语次序,使两个句子或词组,后者成为前者倒文的一种修辞方法。
前后的语词文字相同而词序相逆,排列整齐,变化有序,循环往复,增强了语言的回环美、变异美,灵活生动。
要选择这样的语言符号,构成回文修辞,需要有一定的语言修养,古今的文章中,人们都喜欢运用和阅读。
例如:
【总页数】2页(P20-21)
【作者】刘济民
【作者单位】
【正文语种】中文
【中图分类】H151
【相关文献】
1.成簇的规律间隔短回文重复序列/Cas9系统在病毒领域的应用 [J], 郑庆芬;段钟平;李建生
2.Manacher算法在计算最长回文子串长度中的应用 [J], 唐高阳
3.规律成簇间隔短回文重复-相关核酸内切酶9技术在基因组编辑和基因转录调控中的应用 [J], 管丽红;贾紫森;林俊堂
4.规律成簇间隔短回文重复-相关核酸内切酶9技术在基因组编辑和基因转录调控中的应用 [J], 管丽红;贾紫森;林俊堂
5.规律成簇间隔短回文重复序列及其相关蛋白基因编辑技术在感染性疾病诊断中的应用及其进展 [J], 于佳佳;张旭霞;李传友;刘毅;唐神结
因版权原因,仅展示原文概要,查看原文内容请购买。
manacher算法详解+模板

manacher算法详解+模板manacher算法可以解决字符串的回⽂⼦串长度问题。
个⼈感觉szy学长讲的⾮常好,讲过之后基本上就理解了。
那就讲⼀下个⼈的理解。
(参考了szy学长的ppt)如果⼀个回⽂⼦串的长度是偶数,对称轴会落在两个字符中间。
⾸先两个字符中间的这个位置就很难表⽰。
所以我们在两个字符中间加上没有⽤的字符,⽐如说'#'。
开头结尾也加上。
例如:abcba --> #a#b#c#b#a#这样我们能很⽅便的表⽰每⼀个位置。
manacher算法最终的⽬的是求出⼀个数组pl[i],代表以i为回⽂中⼼(也就是对称轴)的最长回⽂⼦串的回⽂半径。
回⽂半径指的是回⽂⼦串的长度除以⼆之后向上取整。
⽐如:#a#b#a#的回⽂半径就是4。
考虑⽤递推的⽅法求出pl数组。
⾸先我们知道pl[1]=1(特殊记⼀下)。
在递推的过程中维护⼀个np表⽰使i+pl[i]最⼤的⼀个i。
计算f[i]的时候,先考虑使⽤已知的信息求出f[i]。
如果i<=np+pl[np],意味着i被以np为回⽂中⼼的最长回⽂⼦串“覆盖”了。
下⾯的图⽤红⾊表⽰以np为回⽂中⼼的最长回⽂⼦串,⽤绿⾊表⽰以i为回⽂中⼼的最长回⽂⼦串。
这时有两种情况:1.不仅i被覆盖,以i为回⽂中⼼的最长回⽂⼦串也被完全覆盖了。
这个时候由于对称性,pl[i]=pl[2*np-i]。
2.虽然i被覆盖,但是以i为回⽂中⼼的最长回⽂⼦串没有被完全覆盖。
这个时候我们只能保证np+pl[np]以内的对称性(蓝⾊部分)。
也就是说,pl[i]=pl[np]+np-i。
我们并不知道是哪种情况,所以只能对这两种情况分别求值并取其中的最⼩值。
之后如果还能继续向外拓展回⽂部分,就类似暴⼒的做法,⼀下⼀下向外拓展。
最后更新⼀下np。
求出的pl是适⽤于新串的(就是混了⼀堆‘#’的那个串)。
不难发现,对于原串,以i为中⼼的最长回⽂⼦串的长度为pl[i]-1,这个串的开始位置为(i-pl[i])/2+1(向下取整)。
左程云最长回文子串

左程云最长回文子串回文字符串是指正序和倒序读起来都一样的字符串。
例如,"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); }```在上述代码中,我们通过比较记录的最长回文半径和当前回文右边界和当前位置的距离,来寻找最长的回文半径,并更新回文中心和回文右边界的位置。
构造回文子串

构造回文子串构造回文子串回文子串是指正读和反读都一样的字符串,比如“level”、“racecar”等等。
对于许多算法问题而言,都需要对输入的字符串进行回文子串的处理,因此如何快速构造回文子串成为了一个重要的课题。
下面将针对回文子串的构造方法进行分类介绍。
1. 中心扩展法中心扩展法是最简单、最直接的回文子串构造算法,其思路是将每一个字符作为中心向两侧扩展,并判断是否是回文子串。
该算法时间复杂度为O(n^2),其中n为字符串长度。
2. 马拉车算法马拉车算法是一种高效的回文子串构造算法,其基本思想是通过已知回文子串的对称性进行快速扩展。
首先,在原字符串的每个位置插入一个不同的字符,用“#”分隔每个字符,得到一个新的字符串。
例如,原字符串“level”由此变成了“#l#e#v#e#l#”。
接下来,使用“Palindrome Array”数组记录以每个字符为基准可以扩展的最长回文子串的长度,同时记录此时最右侧的回文子串的边界R。
为了避免出界问题,可以在每个字符的左右两侧都加上无限制的字符(例如“#”),初始化Palindrome Array数组时,置所有的元素为0。
在计算过程中,可以首先通过对称性取得与当前字符i的对称点j的“Palindrome Array[i]”的值,然后将其与j到R这一部分的长度相加,得到以i为中心的最大回文子串长度,并更新Palindrome Array[i]的值。
最后,扫描整个Palindrome Array数组,取得最大值,并计算奇偶性确定回文子串的长度,从而在原字符串中找到构成回文子串的部分。
该算法的时间复杂度为O(n),在处理大规模串的回文子串时,极具优势。
3. Manacher算法Manacher算法是对马拉车算法的一次改进,主要在寻找对称点上采用了更高效的方法。
Manacher算法在时间复杂度上与马拉车算法相同(O(n)),但更为简单直观。
Manacher算法仍然将原串插入分隔符“#”中,但是对Palindrome Array的计算过程采用了两个指针,分别指向一个最靠右的已知回文串,和一个最靠右的已知回文串的中心。
Manacher算法(马拉车)

Manacher算法(马拉车)马拉车的解决的问题:给定字符串S,求S中的?解释:回⽂串就是正读反读都⼀样的字符串,⽐如奇回⽂串(bab)、偶回⽂串(noon)。
马拉车算法步骤:1)由于回⽂串存在奇回⽂串和偶回⽂串,马拉车算法第⼀步就是:预处理字符串,做法是在每⼀个字符的左右都加上⼀个特殊字符(前提是这个字符在字符串没有出现过),使这两种回⽂串都变成偶回⽂串。
⽐如加上’#’,这样奇回⽂串(bab)还是会变成奇回⽂串(#b#a#b#),偶回⽂串(noon)会变成奇回⽂串(#n#o#o#n#)。
2)然后我们定义⼀个辅助数组p⽤来表⽰经过与处理过的新字符串t,其中p[i]表⽰以字符t[i]为半径的回⽂⼦串长度,例如:index012345678910111213char$#1#2#2#1#2#2#R12125216123213)找规律规律①:最⼤半径减1等于最长回⽂串的长度看上⾯那个例⼦,以中间的 ‘1’ 为中⼼的回⽂⼦串 “#2#2#1#2#2#” 的半径是6,⽽未添加#号的回⽂⼦串为 “22122”,长度是5,为半径减1。
这是个普遍的规律么?我们再看看之前的那个 “#b#o#b#”,我们很容易看出来以中间的 ‘o’ 为中⼼的回⽂串的半径是4,⽽ "bob"的长度是3,符合规律。
再来看偶数个的情况 “noon”,添加#号后的回⽂串为 “#n#o#o#n#”,以最中间的 ‘#’ 为中⼼的回⽂串的半径是5,⽽ “noon” 的长度是4,完美符合规律。
所以我们只要找到了最⼤的半径,就知道最长的回⽂⼦串的字符个数了。
只知道长度⽆法定位⼦串,我们还需要知道⼦串的起始位置。
规律②:最长回⽂字符的起始位置是中间位置减去半径在除以2我们还是先来看中间的 ‘1’ 在字符串 “#1#2#2#1#2#2#” 中的位置是7,⽽半径是6,貌似 7-6=1,刚好就是回⽂⼦串 “22122” 在原串 “122122” 中的起始位置1。
常见字符串算法

常见字符串算法⼀些基本定义:lcp(s,t) 表⽰两个字符串s,t的最长公共前缀 longest common prefix。
类似的,lcs(s,t) 表⽰s,t的最长公共后缀 longest common suffix。
s[l,r] 和s l,r表⽰字符串s位置l∼r上的字符连接⽽成的⼦串。
若l=1 或r=n则有时省略,即s[,r] 表⽰s长度为r的前缀,s[l,] 表⽰长度为|s|−l+1 的后缀,|s|表⽰字符串s的长度。
真前缀表⽰⾮原串的前缀。
真后缀同理。
Change log2021.12.12. 新增 KMP 算法与 Z 算法。
2021.12.13. 修改部分笔误。
2021.12.23. 新增前⾔。
2021.12.24. 新增 SA 应⽤部分。
2022.1.10 新增⼏个 SA 应⽤与例题。
0. 前⾔⼏乎所有字符串算法都存在⼀个共同特性:基于所求信息的特殊性质与已经求出的信息,使⽤增量法均摊复杂度求得所有信息。
这是动态规划算法的⼜⼀体现。
Manacher 很好地印证了这⼀点:它以所求得的最右回⽂⼦串的回⽂中⼼ d 与回⽂半径 r 为基本,利⽤其回⽂的性质,通过均摊右端点移动距离保证在线性的时间内求得以每个位置为中⼼的最长回⽂半径。
接下来的后缀数组 SA,KMP 算法,Z 算法与后缀⾃动机等常见字符串结构⽆不遵循这⼀规律。
读者在阅读时可以着重体会该技巧,个⼈认为这种思想对于提升解决问题的能⼒有极⼤帮助。
1. Manacher 算法1.1. 算法简介⾸先将字符串所有字符之间(包括头尾)插⼊相同分隔符,因为 Manacher 仅能找到长度为奇数的回⽂串。
并在整个字符串最前⽅额外插⼊另⼀种分隔符,防⽌越界。
设以字符 s i 为对称中⼼的回⽂串中最长的回⽂半径为 p i。
⼀个位置 i 的回⽂半径定义为以 i 为对称中⼼的所有长度为奇数(显然,若⼀个回⽂串以单独某个字符⽽⾮两字符之间的空隙为对称中⼼,其长度必然为奇数)的回⽂串 s[l,r] 两端与 i 的距离 +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];
}
邝斌的ACM模板

字符串处理............................................................................................................................... 2 1、KMP 算法 .................................................................................................................... 2 2、扩展 KMP .................................................................................................................... 5 3、Manacher 最长回文子串 ........................................................................................... 6 4、AC 自动机 ................................................................................................................... 7 5、后缀数组..................................................................................................................... 9 6、后缀自动机....................
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为中⼼的回⽂串⼀点都没匹配,就只能⽼⽼实实⼀个⼀个匹配了。
马拉车算法——精选推荐

马拉车算法Manacher Algorithm算法,俗称马拉车算法,其时间复杂为O(n)。
该算法是利⽤回⽂串的特性来避免重复计算的。
在时间复杂度为O(n^2)的算法中,我们在遍历的过程要考虑到回⽂串长度的奇偶性,⽐如说“abba”的长度为偶数,“abcba”的长度为奇数,这样在寻找最长回⽂⼦串的过程要分别考奇偶的情况,是否可以统⼀处理了?⼀)第⼀步是改造字符串S,变为T,其改造的⽅法如下:在字符串S的字符之间和S的⾸尾都插⼊⼀个“#”,如:S=“abba”变为T="#a#b#b#a#" 。
我们会发现S的长度是4,⽽T的长度为9,长度变为奇数了!!S的长度为奇数时,S=“abcba”,变化为T=“#a#b#c#b#a#”,T的长度为11,所以我们发现其改造可以将字符串的长度变为奇数,这样就可以统⼀的处理奇偶的情况了。
⼆)第⼆步,为了改进回⽂相互重叠的情况,我们将改造完后的T[ i ] 处的回⽂半径存储到数组P[ i ]中(P[ i ]表⽰以字符T[ i ]为中⼼的最长回⽂字串的最端右字符到T[ i ]的长度),这样最后遍历数组P,取其中最⼤值即可。
举⼀个简单的例⼦感受⼀下:数组P有⼀性质,P[ i ] - 1就是该回⽂⼦串在原字符串S中的长度,⾄于证明,⾸先在转换得到的字符串T中,所有的回⽂字串的长度都为奇数,那么对于以T[ i ]为中⼼的最长回⽂字串,其长度就为2*P[ i ]-1, 经过观察可知,T中所有的回⽂⼦串,其中分隔符的数量⼀定⽐其他字符的数量多1,也就是有P[ i ]个分隔符,剩下P[ i ]-1个字符来⾃原字符串,所以该回⽂串在原字符串中的长度就为P[ i ]-1。
另外,由于第⼀个和最后⼀个字符都是#号,且也需要搜索回⽂,为了防⽌越界,我们还需要在⾸尾再加上⾮#号字符,实际操作时我们只需给开头加上个⾮#号字符,结尾不⽤加的原因是字符串的结尾标识为'\0',等于默认加过了。
左程云最长回文子串

左程云最长回文子串
回文串是指正着读和倒着读都一样的字符串,如“level”、“racecar”等。
而回文子串则是指在一个字符串中,连续的一段子串是回文串。
例如,在字符串“babad”中,最长的回文子串是“bab”或“aba”。
左程云是一位著名的算法工程师,他在《程序员代码面试指南》一书中提出了一种求解最长回文子串的算法。
这个算法被称为“Manacher算法”,它的时间复杂度为O(n),是目前已知的最优解。
Manacher算法的核心思想是利用回文串的对称性,避免重复计算。
具体来说,它维护了一个“回文半径数组”,用来记录以每个字符为中心的最长回文半径。
在遍历字符串的过程中,它利用已经计算出来的回文半径,来快速计算当前字符的回文半径。
这样就避免了重复计算,从而达到了O(n)的时间复杂度。
Manacher算法的实现并不复杂,但是需要注意一些细节。
例如,为了方便处理奇偶性,我们需要在字符串中插入一些特殊字符,如“#”等。
另外,我们需要记录当前已知的最长回文子串的起始位置和长度,以便在遍历过程中更新它们。
Manacher算法的应用非常广泛,它可以用来解决很多字符串相关的问题,如最长回文子序列、最长公共子串等。
在实际应用中,我们可以将它用于字符串匹配、文本编辑器、数据压缩等领域。
Manacher算法是一种非常优秀的求解最长回文子串的算法,它的时间复杂度为O(n),并且实现起来也比较简单。
如果你对字符串算法感兴趣,那么一定要学习一下这个算法,它一定会让你受益匪浅。
最长回文子串Manacher 算法

缺陷2)可以通过这个直观的小 体现:c h a r:a b a b ai:01234当i==1,和i==2时,左边的子串aba分别被遍历了一次。
如果我们能改善解法2的不足,就很有希望能提高算法的效率。
Manacher正是针对这些问题改进算法。
(1) 解决长度奇偶性带来的对称轴位置问题Manacher算法首先对字符串做一个预处理,在所有的空隙位置(包括首尾)插入同样的符号,要求这个符号是不会在原串中出现的。
这样会使得所有的串都是奇数长度的。
以插入#号为例:a b a———>#a#b#a#a b b a———>#a#b#b#a#插入的是同样的符号,且符号不存在于原串,因此子串的回文性不受影响,原来是回文的串,插完之后还是回文的,原来不是回文的,依然不会是回文。
(2) 解决重复访问的问题我们把一个回文串中最左或最右位置的字符与其对称轴的距离称为回文半径。
Manacher定义了一个回文半径数组RL,用RL[i]表示以第i个字符为对称轴的回文串的回文半径。
我们一般对字符串从左往右处理,因此这里定义RL[i]为第i个字符为对称轴的回文串的最右一个字符与字符i的距离。
对于上面插入分隔符之后的两个串,可以得到RL数组:c h a r:#a#b#a#R L:1214121R L-1:0103010i:0123456c h a r:#a#b#b#a#R L:121252121R L-1:010141010i:012345678上面我们还求了一下RL[i]-1。
通过观察可以发现,RL[i]-1的值,正是在原本那个没有插入过分隔符的串中,以位置i为对称轴的最长回文串的长度。
那么只要我们求出了RL数组,就能得到最长回文子串的长度。
于是问题变成了,怎样高效地求的RL数组。
基本思路是利用回文串的对称性,扩展回文串。
我们从左往右地访问字符串来求RL,假设当前访问到的位置为i,即要求RL[i],在对应上图,i必然是在p o右边的(obviously)。
回文子串数目

回文子串数目回文子串指的是正读和反读都是相同的字符串。
求一个字符串中回文子串的数量是一个经典的问题,在这篇文章中,我们将详细讨论这个问题,并介绍几种解决方法。
首先,我们来看一些示例。
对于字符串"abcba",它的回文子串有"a"、"b"、"c"、"b"、"a"、"abcba",一共有6个。
对于字符串"aaa",它的回文子串有"a"、"a"、"a"、"aa"、"aa"、"aaa",一共有6个。
解决这个问题的一种简单方法是枚举所有可能的子串,然后判断是否是回文。
这种方法的时间复杂度是O(n^3),其中n是字符串的长度。
具体的实现如下所示:```pythondef countPalindromeSubstrings(s: str) -> int:count = 0n = len(s)for i in range(n):for j in range(i, n):if isPalindrome(s[i:j+1]):count += 1return countdef isPalindrome(s: str) -> bool:return s == s[::-1]```上述代码中的函数`countPalindromeSubstrings`用于计算字符串中的回文子串数量,而函数`isPalindrome`用于判断一个字符串是否是回文。
这种方法虽然简单,但是时间复杂度较高,当字符串长度很大时,计算量将会非常大。
因此,我们需要寻找更加高效的解决方法。
动态规划是解决这个问题的一种常见方法。
利用动态规划,我们可以通过已知的信息来计算未知的信息。
回文串划分

回文串划分
摘要:
一、回文串的概念
1.回文串的定义
2.回文串的例子
二、回文串的划分方法
1.基于最长相同前缀的划分方法
2.Manacher 算法
3.回文树算法
三、回文串的应用
1.文本压缩
2.模式识别
3.密码学
正文:
回文串是指正序和倒序都相同的字符串,例如“level”和“racecar”。
在计算机科学中,回文串有着广泛的应用,例如文本压缩、模式识别和密码学等。
因此,对回文串进行有效的划分具有重要的实际意义。
回文串的划分方法有很多种,这里主要介绍三种常用的方法。
首先,是基于最长相同前缀的划分方法。
这种方法的基本思想是找到回文串中最长的相同前缀,然后将回文串划分成两个部分,其中一部分包含该最长相同前缀。
例如,对于回文串“abcdcba”,最长相同前缀是“ab”,所以将
回文串划分成“ab”和“cdcba”。
其次,是Manacher 算法。
这是一种线性时间复杂度的回文串划分算法,适用于大规模文本处理。
Manacher 算法通过维护一个“核心”和一个“窗口”,在不断扩展“窗口”的同时,计算回文串的“奇偶性”。
当“窗口”大小达到回文串长度的一半时,算法结束,输出回文串的划分。
最后,是回文树算法。
这种方法将回文串看作是一棵回文树,其中每个节点表示一个字符,每个内部节点有两个子节点,分别表示该节点的字符与其相反字符。
通过遍历回文树,可以找到回文串的划分。
总的来说,回文串划分是计算机科学中的一个重要问题,有着广泛的应用。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号。
比如abba 变成#a#b#b#a#,aba变成
#a#b#a#。
为了进一步减少编码的复杂度,可以在字符串的开始加入另一个特殊字符,这样就不用特殊处理越界问题,比如$#a#b#a#(注意,下面的代码是用C语言写就,由于C 语言规范还要求字符串末尾有一个'\0'所以正好OK,但其他语言可能会导致越界)。
下面以字符串12212321为例,经过上一步,变成了S[] = "$#1#2#2#1#2#3#2#1#";
然后用一个数组P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括
S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系:
S # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #
P 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1
(p.s. 可以看出,P[i]-1正好是原字符串中回文串的总长度)
那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,其中id 为已知的{右边界最大} 的回文子串的中心,mx则为id+P[id],也就是这个子串的右边界。
然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx > i,那么
P[i] >= MIN(P[2 * id - i], mx - i)。
就是这个串卡了我非常久。
实际上如果把它写得复杂一点,理解起来会简单很多:
//记j = 2 * id - i,也就是说j 是i 关于id 的对称点(j = id - (i - id))
if (mx - i > P[j])
P[i] = P[j];
else /* P[j] >= mx - i */
P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。
当然光看代码还是不够清晰,还是借助图来理解比较容易。
当mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于i 和j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有P[i] = P[j],见下图。
当P[j] >= mx - i 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说P[i] >= mx - i。
至于mx 之后的部分是否对称,就只能老老实实去匹配了。
对于mx <= i 的情况,无法对P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。
于是代码如下:
//输入,并处理得到字符串s
int p[1000], mx = 0, id = 0;
memset(p, 0, sizeof(p));
for (i = 1; s[i] != '\0'; i++) {
p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
while (s[i + p[i]] == s[i - p[i]]) p[i]++;
if (i + p[i] > mx) {
mx = i + p[i];
id = i;
}
}
//找出p[i]中最大的。