最长回文子串 —— Manacher (马拉车) 算法
最长回文子串
回文串就是原串和反转字符串相同的字符串。比如 aba,acca。前一个是奇数长度的回文串,后一个是偶数长度的回文串。
最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串。
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\times 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 最长回文
#include <bits/stdc++.h>
using namespace std;
const int maxn = 220000;
string init(string s) {
string res;
res += '@';
for(int i = 0; i < s.size(); ++i) {
res += '#';
res += s[i];
}
res += '#';
res += '$';
return res;
}
int len[maxn];
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;
}
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() {
ios::sync_with_stdio(false);
cin.tie(0);
string s;
while (cin >> s) {
string tmp = init(s);
cout << Manacher(tmp) << endl;
}
return 0;
}
参考
最长回文子串 —— Manacher (马拉车) 算法的更多相关文章
- 最长回文子串——manacher
最长回文子串--Manacher 算法 (原版的博主的代码都是用py写的,这里改成c++) c++ 算法 字符串处理 0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一 ...
- lintcode最长回文子串(Manacher算法)
题目来自lintcode, 链接:http://www.lintcode.com/zh-cn/problem/longest-palindromic-substring/ 最长回文子串 给出一个字符串 ...
- 最长回文子串Manacher算法模板
Manacher算法能够在O(N)的时间复杂度内得到一个字符串以任意位置为中心的回文子串.其算法的基本原理就是利用已知回文串的左半部分来推导右半部分. 首先,在字符串s中,用rad[i]表示第i个字符 ...
- 九度OJ 1528 最长回文子串 -- Manacher算法
题目地址:http://ac.jobdu.com/problem.php?pid=1528 题目描述: 回文串就是一个正读和反读都一样的字符串,比如"level"或者"n ...
- 最长回文子串—Manacher 算法 及 python实现
最长回文子串问题:给定一个字符串,求它的最长回文子串长度.如果一个字符串正着读和反着读是一样的,那它就是回文串. 给定一个字符串,求它最长的回文子串长度,例如输入字符串'35534321',它的最 ...
- hihocoder #1032 : 最长回文子串 Manacher算法
题目链接: https://hihocoder.com/problemset/problem/1032?sid=868170 最长回文子串 时间限制:1000ms内存限制:64MB 问题描述 小Hi和 ...
- 5. Longest Palindromic Substring(最长回文子串 manacher 算法/ DP动态规划)
Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...
- HiHo 1032 最长回文子串 (Manacher算法求解)
/** * 求解最长回文字串,Manacher算法o(n)求解最长回文子串问题 **/ #include<cstdio> #include<cstdlib> #include& ...
- hihoCoder #1032 : 最长回文子串 [ Manacher算法--O(n)回文子串算法 ]
传送门 #1032 : 最长回文子串 时间限制:1000ms 单点时限:1000ms 内存限制:64MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相 ...
随机推荐
- 线程休眠只会用 Thread.sleep?来,教你新姿势!
线程休眠是 Java 开发经常会用到的一个手段,就是让当前线程睡一会儿,睡醒之后再继续运行. 咱大多数程序员,多线程虽然学得不好,但线程休眠,无人不知,无人不晓,也都会用,不就是用 Thread.sl ...
- Spring cloud 注册服务小结
服务注册中心:Eureka.Zookeeper.Cousul.Nacos 使用RestTemplate.openFeign做服务调用,底层使用的是Ribbon. Ribbon做了负载均衡,也可以做一个 ...
- hdu6354 Everything Has Changed (圆的相交弧长)
题目传送门 题意: 用一堆圆来切割一个圆心为原点,半径为R的圆A,问切割完毕后圆A外围剩余部分的周长(图中的红线部分). 思路: 首先判定圆与圆A的关系,这题我们只需要与A内切.相交的圆. 然后就是求 ...
- Task总结
1.Task的优势 ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便.比如: ◆ ThreadPool不支持线程的取消.完成.失败通知等交互性 ...
- Linux查看关闭进程
ps:进程的静态列表(Process status) - PID:进程号,每个进程独一无二的标识符(关闭进程需要使用) - TTY:终端所属,表明进程产生于哪一个终端,对于多用户使用的Linux服务器 ...
- UIView与CALayer 区别
在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮.一个文本标签.一个文本输入框.一个图标等等,这些都是UIView. 其实UIView之所以能显示在屏幕上,完全是因为它内部的一个 ...
- OC学习--OC中的类--类的定义,成员变量,方法
1. 类的定义 >用关键字@interface进行声名 @end 结束 >所有的类有一个基类NSobject >类名 也是标示符 第一个字母大写 如果多个字母组成 每个单词的首字母 ...
- PHP中的魔术方法总结:__construct,__destruct ,__call,__callStatic,__get,__set,__isset, __unset ,__sleep,__wakeup,__toString,__set_state,__clone,__autoload
1.__get.__set这两个方法是为在类和他们的父类中没有声明的属性而设计的__get( $property ) 当调用一个未定义的属性时访问此方法__set( $property, $value ...
- ssh - OpenSSH SSH 客户端 (远程登录程序)
总览 (SYNOPSIS) ssh [-l login_name ] hostname | user@hostname [command ] ssh -words [-afgknqstvxACNTX1 ...
- Hibernate 一对一(基于唯一外键的关联)
主表 hbm.xml中 使用<one-to-one> 从表hbm.xml中使用<many-to-one> 并指定unique=true people.hbm.xml: < ...