Manacher算法可以在\(O(N)\)时间内求解出一个字符串的所有回文子串(正反遍历相同的字串)

注:回文串显然有两种,一种是奇数长度,如abczcba,有一个中心字符z;另外一种是偶数个长度,如abccba,没有中心字符,下面提到暂时都是只查找奇数长度的字符串

要理解Manacher算法,首先假象一个随机生成的字符串,枚举每个字符作为中心,向两边不断拓展,判断是否相等,直到两边不相等或者走到边界为止,就可以得到每个字符为中心的最大回文长度了(记作\(f_i\)),显然第\(i\)个字符加上左右\(f_i\)的字符就可以构成\(i\)为中心的最长回文串\(S_{[i-f_i+1, i+f_i-1]}\),而\(i\)为中心的更短的字串,显然也是回文串了,这样就求出了所有回文串。

void BF(char *s, int len, int *f) {
for (int i = 1; i <= len; i++) {
f[i] = 1;
while (s[i+f[i]] == s[i-f[i]]) ++f[i];
}
}

但是如果字符大量重叠(如abababab...),几乎每次拓展都会拓展到最边界,效率就会达到平方级。解决这个问题,就应该想到利用回文串的性质,利用已经得到的\(f_i\)来推出当前的\(f_i\)。

那么回文串有什么性质?首先是对称下标构成的字串肯定对称(比如abczcba,\(S_{[1, 3]}=S_{[7, 4]}\)),而且回文串的对称串依然是自己(定义)。所以,一个回文串中如果有另一个回文串,那么子串的对称下标肯定也构成了一个相同的回文串,可以直接推出来。换言之,如果我们知道一个回文串内左半边的对称串,就可以直接得到他右半边的对称串

所以我们记录下达到过的最靠右的点和他的中心点(记为\(maxR\)和\(mid\),当然你也可以只记录\(mid\),用\(mid+f[mid]\)表示右边界)。只要枚举的\(i\)还在右边界以内,就尝试用\(f[mid*2-i]\)来更新\(f[i]\)。当然如果左半边回文串的最长左边界\(i-f[i]+1\)已经不在\(mid\)的回文范围内了,那就顶到最左边,把\(f[i]\)更新为\(f[mid]+mid-i\)(也就是最右边\(maxR\)处)。这时就需要继续往后更新,将\(maxR\)往右拓展。

这里是一份参考代码,这里\(maxR\)是开区间。和上面的暴力一样没有做边界特判,应该将\(S[0]和S[len+1]\)设为两个原字符串中没有且不相等的字符(否则可能加上两端,多一个回文串,然后再往外走导致越界)。显然\(f[i]\)取\(f[mid]+mid-i\)时才会执行之后的更新。

void Manacher(char *s, int len, int *f) {
static int maxR, mid;
for (int i = 1; i <= len; i++) {
f[i] = (i < maxR) ? min(f[mid*2-i], f[mid]+mid-i) : 1;
while (s[i+f[i]] == s[i-f[i]]) ++f[i];
if (i + f[i] > maxR)
maxR = (mid = i) + f[i];
}
}

那么为什么只需要这一个优化就可以做到线性呢?因为这样已经可以做到每个字符只被访问一次(算上和后面的字符比较相等就是两次)。回看第一张图,\(maxR\)左侧是已经访问过的,右侧是没有访问过的。这些已经访问过但\(i\)还没有遍历到的位置都可以\(O(1)\)求解,而\(maxR\)只会不断向右移动,因此一定是\(O(N)\)的。其本质就是利用回文串的性质避免了\(mid\)到\(maxR\)处的所有计算,只在往后更新的时候计算。

最后谈谈偶数长度回文串,偶数长度串的“中心”相当于于是字符之间的空隙。处理它们的一种方法是在每两个字符串之间加入不存在于原字符串的字符(如#),然后在执行算法,此时以#为中心的回文串就是偶数回文串。或者先做奇数长度,然后找到所有满足\(S[i]==S[i+1]\)的下标,将他们看作“一个中心“后进行拓展。

for (int i = 1; i <= len; i++)
s0[(i << 1) - 1] = s[i], s0[i << 1] = '#';

最新文章

  1. haxe jni调用输入法
  2. IOS managerTime
  3. va_list结构体
  4. C# 通过服务启动窗体(把窗体添加到服务里)实现用户交互的windows服务[转发]
  5. A+B
  6. [软件测试]Linux环境中简单清爽的Google Test (GTest)测试环境搭建(初级使用)
  7. 利用DataTable快速批量导数据
  8. HTML+CSS学习笔记(9)- CSS的继承、层叠和特殊性
  9. Linux 命令 - curl: transfer a URL
  10. UGUI之在场景中设置、修改标签和按钮
  11. freemarker定义自己的标签错误(八)
  12. 面向对象重写(override)与重载(overload)区别
  13. Omi全新版本来袭 - 指令系统
  14. 1.如何安装matlab2016a
  15. 剑指offer 14:链表中倒数第k个节点
  16. Java基础IO流(三)字符流
  17. Redis防止重複請求鎖功能
  18. Linux I/O 调度器
  19. ROSETTA使用技巧随笔--score.sc处理
  20. kali linux 压缩文件解压缩命令(包含7z)

热门文章

  1. SQL优化一例:通过改变分组条件(减少计算次数)来提高效率
  2. 解决H5设置了line-height但并没有居中的问题
  3. jQuery 实现列表自动滚动循环滚动显示新闻通知
  4. 【LeetCode】985. Sum of Even Numbers After Queries 解题报告(C++)
  5. 【LeetCode】932. Beautiful Array 解题报告(Python)
  6. 【LeetCode】840. Magic Squares In Grid 解题报告(Python)
  7. 第四个知识点 P类复杂问题
  8. TensorFlow.NET机器学习入门【7】采用卷积神经网络(CNN)处理Fashion-MNIST
  9. CLION 使用自己的makefile来运行
  10. Vulnhub实战-rtemis靶机&#128123;