HELLO WORLD--一起加|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-08-03 11:37阅读: 77评论: 0推荐: 0

字符串 _ 马拉车(Manacher)算法

概述

马拉车(Manacher)是查找一个字符串的最长回文子串的线性算法。

同时还可以用于求所有回文子串数量

算法原理与实现

计算字符串的最长回文字串的朴素算法:
枚举回文串的中点,并且分为两种情况:

  • 一种是回文串长度是奇数的情况
  • 另一种是回文串长度是偶数的情况

时间复杂度为O(n2).

马拉车算法是快速求长度为奇数的回文串的方法。
但是通过转化可以将长度为偶数的回文串转化为奇数长度。

解决奇偶数问题

目的: 将偶数回文串转化为奇数回文串。
具体做法:

  1. 在原字符串的每个相邻两个字符中间插入一个分隔符,
  2. 在首尾也要添加一个分隔符

分隔符的要求不在原串中出现,一般情况下可以用#号。

此时:
=×2+1(’#’ 个数始终比原字符个数多 1)。
设某个偶数回文串半径为r,则他在新串的长度为:Len=(r×2)×2+1一定为奇数。
而且:
通过匹配得到新的字符串的最大回文串的半径R也能得到旧的回文串长度len: len=R+1

举一个例子:
image

算法核心

Manacher算法用一个辅助数组Len[i]表示:
以字符S[i]为中心的最长回文字串的半径。

从前向后遍历
我们维护一个右边界最靠右的回文串的中心点mid和左端点L,和右端点R。

我们可以发现(还是很显然的,可以手动验证下):

当i在我们维护的右端点R右边时,没有规律,只能暴力求。

当i在我们维护的右端点R左边时,我们找到节点i的通过mid对称的节点x,如果x的左端点在我们维护的L的

  1. 右边,则当前节点i和最长回文串和x的相同。

  2. 左边,则当前节点i的最长回文串的半径就是i到维护的右端点R的距离。

  3. 上,则当前节点回文串的半径至少和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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

可以看到代码有两层循环,但是因为:对于外层和内层循环都不会回溯,都只会执行O(n)次。
所以算法整体复杂度为O(n)

对于上面的例子,可以得出Len[i]数组为:
image

应用

求最长回文子串 (模板代码)

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;
}

求所有回文子串数量

G KFC Crazy Thursday 马拉车算法

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/15882207.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(77)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起