最长回文子串

回文串就是原串和反转字符串相同的字符串。比如 abaacca。前一个是奇数长度的回文串,后一个是偶数长度的回文串。

最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串。

Brute Force 做法

枚举所有子串,判断是否是回文串,然后寻找最大长度。寻找所有子串要两重循环,判断是否是回文要一重循环,总体时间复杂度 \(O(n^3)\)。

稍微优化一下,可以枚举对称中心,然后向两边扩展,直到遇到两个不同的字符,枚举下一个对称中心,寻找其中的最大长度,时间复杂度 \(O(n^2)\)。

还可以使用 DP 解决,求原串与反转字符串的最长公共子序列 (LCS),时间复杂度 \(O(n^2)\)。

Manacher 算法

接下来就是重点了,Manacher 算法,在1975年由一个叫 Manacher 的人发明的。能够在 O(n) 的时间求得最长回文子串。

前面提到,回文串有奇数长度的和偶数长度的,分类讨论有些复杂,可以参考这里。为了避免分类讨论,可以使用一个技巧:在字符串首尾以及每两个字符之间插入一个 '#'。比如 abaacca,转换后就是 #a#b#a#a#c#c#a#。那么不管是奇回文 aba 还是偶回文 acca,转换后都是奇回文 (#a#b#a##a#c#c#a#)。

string init(string s) {
string res;
res += '@'; // 在开头加入哨兵防止越界
for(int i = 0; i < s.size(); ++i) {
res += '#';
res += s[i];
}
res += '#';
res += '$'; // 结尾同样加入哨兵防止越界
return res;
}

\(Manacher\) 算法的思想来自于上述枚举对称中心的思想。该算法需要维护一个 \(len\) 数组,\(len[i]\) 代表 \(i\) 为中心的最长回文子串的长度。

设 \(s\) 为原字符串,\(mx\) 为之前计算的回文串中右端点的最大值,这个回文串的中心位置为 \(id\),也就是 \(mx=id+len[id]\)。

每次计算的时候,id 的右边和左边是对称的,因此计算右边的时候不需要用从对称中心向两边扩展的思想,而是只用一行代码解决:len[i] = min(mx - i, len[2 * id - i]);,这也是 Manacher 中最关键的一行代码。

如下图所示,\(id\) 右边到 \(mx\) 之间的子串与 \(id\) 左边是对称的,所以右边的 \(len[i]\) 最大长度为左边与之对称的 \(len[2×id−i]\),由于右边的回文串不能超过 \(mx\) (原因见第 2 张图),所以 len[i] = min(mx - i, len[2 * id - i]);

\(id\) 右边的回文串长度不能超过 \(mx−i\) 的原因是,如果 \(len[2∗id−i]\) 更长,如下图的黄色部分,那么右边的黄色部分与左边的黄色部分相同,那么黑色部分应该可以更长,产生矛盾。

理解了上面的内容基本上就理解了 \(Manacher\) 算法了。

代码实现如下:

如果觉得这个代码解释的不太清楚的话可以直接看下面的模板题或者看下刘毅学长的代码

int Manacher(string s) {
memset(len, 0, sizeof(len));
int mx = 0, id = 0;
int ans = 0;
for(int i = 1; i < s.size() - 1; ++i) {
if(mx > i) {
len[i] = min(mx - i, len[2 * id - i]); // 上面提到的最关键的一行代码
} else {
len[i] = 1; // 如果 i 超过右边界要从头计算
}
while(s[i - len[i]] == s[i + len[i]]) { // 从头计算的方法,就是上面提到的从中心向两边扩展
++len[i];
}
// 更新 mx 和 id
if(i + len[i] > mx) {
mx = i + len[i];
id = i;
}
ans = max(ans, len[i]);
}
return ans - 1; // len[i] 中的最大值-1 即为原串的最长回文子串长度
}

模板题:HDU 3068 最长回文

题目链接:HDU 3068 最长回文

// Author : RioTian
// Time : 20/11/11
#include <bits/stdc++.h>
#define ms(a, b) memset(a, b, sizeof(a))
using namespace std;
const int maxn = 220000;
int len[maxn];
string init(string s) {
string res;
res += '@';
for (int i = 0; i < s.size(); ++i) {
res += '#';
res += s[i];
}
res += '#';
res += '$';
return res;
}
int Manacher(string s) {
ms(len, 0);
int mx = 0, id = 0;
int ans = 0;
for (int i = 1; i < s.size() - 1; ++i) {
if (mx > i)
len[i] = min(mx - i, len[2 * id - i]);
else
len[i] = 1; while (s[i - len[i]] == s[i + len[i]]) ++len[i]; if (i + len[i] > mx) {
mx = i + len[i];
id = i;
}
ans = max(ans, len[i]);
}
return ans - 1;
} int main() {
// freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
string s;
while (cin >> s) {
string tmp = init(s);
cout << Manacher(tmp) << endl;
}
}

参考

OI wiki

刘毅学长关于马拉车算法的讲解

Manacher (马拉车) 算法:解决最长回文子串的利器的更多相关文章

  1. 使用manacher算法解决最长回文子串问题

    要解决的问题 求一个字符串最长回文子串是什么.且时间复杂度 O(N) 具体描述可参考: LeetCode_5_最长回文子串 LintCode_200_最长回文子串 暴力解法 以每个字符为中心向左右两边 ...

  2. 小白月赛13 B小A的回文串 (马拉车算法求最长回文子串)

    链接:https://ac.nowcoder.com/acm/contest/549/B来源:牛客网 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262144K,其他语言52428 ...

  3. Manacher's Algorithm 马拉车算法(最长回文串)

    这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...

  4. Manacher算法(马拉车)求最长回文子串

    Manacher算法求最长回文字串 算法思路 按照惯例((・◇・)?),这里只是对算法的一些大体思路做一个描述,因为找到了相当好理解的博客可以参考(算法细节见参考文章). 一般而言,我们的判断回文算法 ...

  5. Manacher算法——求最长回文子串

    首先,得先了解什么是回文串.回文串就是正反读起来就是一样的,如“abcdcba”.我们要是直接采用暴力方法来查找最长回文子串,时间复杂度为O(n^3),好一点的方法是枚举每一个字符,比较较它左右距离相 ...

  6. manacher算法求最长回文子串

    一:背景 给定一个字符串,求出其最长回文子串.例如: s="abcd",最长回文长度为 1: s="ababa",最长回文长度为 5: s="abcc ...

  7. Manacher算法 求 最长回文子串

    1 概述(扯淡) 在了解Manacher算法之前,我们得先知道什么是回文串和子串. 回文串,就是正着看反着看都一样的字符串.比如说"abba"就是一个回文串,"abbc& ...

  8. Manacher算法 - 求最长回文串的利器

    求最长回文串的利器 - Manacher算法 Manacher主要是用来求某个字符串的最长回文子串. 不要被manacher这个名字吓倒了,其实manacher算法很简单,也很容易理解,程序短,时间复 ...

  9. Manacher模板( 线性求最长回文子串 )

    模板 #include<stdio.h> #include<string.h> #include<algorithm> #include<map> us ...

随机推荐

  1. 可能是东半球第二好用的软件工具全部在这里(update in 2020.10.09)

    1. 产品经理工具种草 浏览器:Google Chrome 网络浏览器 原型绘制软件:墨刀- 在线产品原型设计与协作平台(https://modao.cc/).摹客mockplus - 摹客,让设计和 ...

  2. 【从零开始撸一个App】Kotlin

    工欲善其事必先利其器.像我们从零开始撸一个App的话,选择最合适的语言是首要任务.如果你跟我一样对Java蹒跚的步态和僵硬的语法颇感无奈,那么Kotlin在很大程度上不会令你失望.虽然为了符合JVM规 ...

  3. CodeForces 79D 【Password】,洛谷P3943 【星空】

    其实我做的是洛谷的P3943,但是听说fstqwq窃题...... 题目描述: 小 C 拿来了一长串星型小灯泡,假装是星星,递给小 F,想让小 F 开心一点.不过,有 着强迫症的小 F 发现,这串一共 ...

  4. Windows Server 2003 蓝屏 -- 系统故障:停止错误

    Windows Server 2003  EE 出现蓝屏: 0X0000004D (0X000f27D9, 0X000F27D9, 0X0000000C, 0X00000000) 蓝屏拍照: 重启机器 ...

  5. 实验 6:OpenDaylight 实验——OpenDaylight 及 Postman 实现流表下发

    一.实验目的 熟悉 Postman 的使用:熟悉如何使用 OpenDaylight 通过 Postman 下发流表. 二.实验任务 流表有软超时和硬超时的概念,分别对应流表中的 idle_timeou ...

  6. Dotnet Core使用特定的SDK&Runtime版本

    Dotnet Core的SDK版本总在升级,怎么使用一个特定的版本呢?   假期过完了,心情还在.今天写个短的. 一.前言 写这个是因为昨天刷微软官方文档,发现global.json在 SDK 3.0 ...

  7. 用网桥和veth实现容器的桥接模式

    原理图如下 具体命令先不写了,有时间再写,主要还是用的上一篇说的知识.

  8. 手把手教你用 Spring Boot搭建一个在线文件预览系统!支持ppt、doc等多种类型文件预览

    昨晚搭建环境都花了好一会时间,主要在浪费在了安装 openoffice 这个依赖环境上(Mac 需要手动安装). 然后,又一步一步功能演示,记录,调试项目,并且简单研究了一下核心代码之后才把这篇文章写 ...

  9. 关于umi-request GET请求参数携带数组

    在使用umi-request时候发现GET传递数组,后台在接收时只能接受到最后一个,对此记录一下. 问题: // 发送数据: request(url, { params: { select: [1,2 ...

  10. 【C语言C++编程入门】——编译机制和语言标准!

    编程机制 编写程序时必须遵循确切步骤主要是取决于你的计算机环境.因为 C语言是可以移植的,所以它在许多环境中可用,其中包括 UNIX,Linux,Windows等等 . 不过,让我们首先来看一看许多环 ...