字符串 _ 马拉车(Manacher)算法
概述
马拉车(Manacher)是查找一个字符串的最长回文子串的线性算法。
同时还可以用于求所有回文子串数量。
算法原理与实现
计算字符串的最长回文字串的朴素算法:
枚举回文串的中点,并且分为两种情况:
- 一种是回文串长度是奇数的情况
- 另一种是回文串长度是偶数的情况
时间复杂度为.
马拉车算法是快速求长度为奇数的回文串的方法。
但是通过转化可以将长度为偶数的回文串转化为奇数长度。
解决奇偶数问题
目的: 将偶数回文串转化为奇数回文串。
具体做法:
- 在原字符串的每个相邻两个字符中间插入一个分隔符,
- 在首尾也要添加一个分隔符
分隔符的要求不在原串中出现,一般情况下可以用#号。
此时:
(’#’ 个数始终比原字符个数多 1)。
设某个偶数回文串半径为r,则他在新串的长度为:一定为奇数。
而且:
通过匹配得到新的字符串的最大回文串的半径R也能得到旧的回文串长度len:
举一个例子:
算法核心
Manacher算法用一个辅助数组Len[i]表示:
以字符S[i]为中心的最长回文字串的半径。
从前向后遍历
我们维护一个右边界最靠右的回文串的中心点mid和左端点L,和右端点R。
我们可以发现(还是很显然的,可以手动验证下):
当i在我们维护的右端点R右边时,没有规律,只能暴力求。
当i在我们维护的右端点R左边时,我们找到节点i的通过mid对称的节点x,如果x的左端点在我们维护的L的:
-
右边,则当前节点i和最长回文串和x的相同。
-
左边,则当前节点i的最长回文串的半径就是i到维护的右端点R的距离。
-
上,则当前节点回文串的半径至少和x相同。
此时更新我们的mid,L和R。
代码就是:
int Manacher() { int len = Init(); // 取得新字符串长度并完成向 s_new 的转换 int max_len = -1; // 最长回文长度 int id;//id为i和j的中心点 i以 id 为对称点翻折到j的位置 int mx = 0;// mx 代表以 id 为中心的最长回文的右边界 for (int i = 1; i < len; i++) { if (i < mx)//mx 代表以 id 为中心的最长回文的右边界 p[i] = min(p[2 * id - i], mx - i); // 2 * id - i 为 i 关于 id 的对称点 else p[i] = 1;//超过边界总共就不是回文了 while (s_new[i - p[i]] == s_new[i + p[i]]) // 不需边界判断,因为左有 $,右有 ^ 二者必不可能相等 p[i]++; // 我们每走一步 i,都要和 mx 比较,我们希望 mx 尽可能的远, // 这样才能更有机会执行 if (i < mx)这句代码,从而提高效率 if (mx < i + p[i])//i的位置再加上i对应的回文半径即为最远的边界 { id = i; mx = i + p[i]; } max_len = max(max_len, p[i] - 1);// p[i]-1 即为原字符串中最长回文串的长度 } return max_len; } 作者:Cold 链接:https://www.acwing.com/solution/content/46083/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
可以看到代码有两层循环,但是因为:对于外层和内层循环都不会回溯,都只会执行次。
所以算法整体复杂度为
对于上面的例子,可以得出Len[i]数组为:
应用
求最长回文子串 (模板代码)
acwing:https://www.acwing.com/problem/content/3190/
洛谷:https://www.luogu.com.cn/problem/P3805
#include <bits/stdc++.h> #define endl '\n' #define int long long #define pii pair<int, int> #define pll pair<int, int> #define ull unsigned long long using namespace std; const int N = 2e7 + 10; char a[N],b[N]; int p[N]; int n; void init(){ int k=0; b[k++]='$',b[k++]='#'; for(int i=0;i<n;i++) b[k++]=a[i],b[k++]='#'; b[k++]='^'; n=k; } void manacher(){ int mr=0,mid; for(int i=0;i<n;i++){ if(i<mr) p[i]=min(p[mid*2-i],mr-i); else p[i]=1; while(b[i-p[i]]==b[i+p[i]]) p[i]++; if(i+p[i]>mr){ mr=i+p[i]; mid=i; } } } void solve(){ cin>>a; n=strlen(a); init(); manacher(); int ans=0; for(int i=0;i<n;i++) ans=max(ans,p[i]); cout<<ans-1<<endl; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t = 1; // cin >> t; while (t--) solve(); return 0; }
求所有回文子串数量
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/15882207.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步