最长回文子串

回文串就是原串和反转字符串相同的字符串。比如 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\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 (马拉车) 算法的更多相关文章

  1. 最长回文子串——manacher

    最长回文子串--Manacher 算法 (原版的博主的代码都是用py写的,这里改成c++) c++ 算法 字符串处理 0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一 ...

  2. lintcode最长回文子串(Manacher算法)

    题目来自lintcode, 链接:http://www.lintcode.com/zh-cn/problem/longest-palindromic-substring/ 最长回文子串 给出一个字符串 ...

  3. 最长回文子串Manacher算法模板

    Manacher算法能够在O(N)的时间复杂度内得到一个字符串以任意位置为中心的回文子串.其算法的基本原理就是利用已知回文串的左半部分来推导右半部分. 首先,在字符串s中,用rad[i]表示第i个字符 ...

  4. 九度OJ 1528 最长回文子串 -- Manacher算法

    题目地址:http://ac.jobdu.com/problem.php?pid=1528 题目描述: 回文串就是一个正读和反读都一样的字符串,比如"level"或者"n ...

  5. 最长回文子串—Manacher 算法 及 python实现

    最长回文子串问题:给定一个字符串,求它的最长回文子串长度.如果一个字符串正着读和反着读是一样的,那它就是回文串.   给定一个字符串,求它最长的回文子串长度,例如输入字符串'35534321',它的最 ...

  6. hihocoder #1032 : 最长回文子串 Manacher算法

    题目链接: https://hihocoder.com/problemset/problem/1032?sid=868170 最长回文子串 时间限制:1000ms内存限制:64MB 问题描述 小Hi和 ...

  7. 5. Longest Palindromic Substring(最长回文子串 manacher 算法/ DP动态规划)

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...

  8. HiHo 1032 最长回文子串 (Manacher算法求解)

    /** * 求解最长回文字串,Manacher算法o(n)求解最长回文子串问题 **/ #include<cstdio> #include<cstdlib> #include& ...

  9. hihoCoder #1032 : 最长回文子串 [ Manacher算法--O(n)回文子串算法 ]

    传送门 #1032 : 最长回文子串 时间限制:1000ms 单点时限:1000ms 内存限制:64MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相 ...

随机推荐

  1. TCL自动化之SSH交互式

    目前ssh工具很多,但是能够轻松运用到自动化脚本中,可以轻松适配任何环境,满足ssh交互式登录的tcl工具包很少 下面是个人在tcl自动化过程中比较满意的一款自动化脚本 通过使用管道方式分装plink ...

  2. python 实现加法

    https://ac.nowcoder.com/acm/contest/338/G 链接:https://ac.nowcoder.com/acm/contest/338/G来源:牛客网 题目描述 Th ...

  3. 消息中间件-技术专区-RocketMQ架构原理

    RocketMQ是阿里开源的分布式消息中间件,跟其它中间件相比,RocketMQ的特点是纯JAVA实现:集群和HA实现相对简单:在发生宕机和其它故障时消息丢失率更低. 一.RocketMQ专业术语 P ...

  4. 【学习总结】Eclipse常用快捷键

    相关博文 [JAVA]eclipse-Introduction

  5. webpack的理解、总结

    weabpck的基础应用 https://blog.zhangjd.me/2016/06/19/webpack-your-bags/ https://juejin.im/post/5cc26dfef2 ...

  6. 使用EntityFramework调用存储过程并获取存储过程返回的结果集

    [实习]刚入职,公司要求完成两个任务,任务要求使用存储过程和事务,其中一个问题要获取存储过程的查询结果集.经过多方查找和自己的实践,终于找到了方法.这里记录一下. 看到的这篇文章中给出的例子是查询单个 ...

  7. CentOS7单用户模式修改密码

    以下内容均摘抄自:https://blog.csdn.net/ywd1992/article/details/83538730  亲测有用,谢谢大佬的好文章 1.启动centos系统,并且当在GRUB ...

  8. 工作中常用的linux命令大全

    文章内容参考:https://www.cnblogs.com/yjd_hycf_space/p/7730690.html  谢谢大佬的分享 系统信息  date  显示系统日期 cal + 年份 显示 ...

  9. Spark 读取HBase数据

    Spark1.6.2 读取 HBase 1.2.3 //hbase-common-1.2.3.jar //hbase-protocol-1.2.3.jar //hbase-server-1.2.3.j ...

  10. fork/join并发编程

    Fork & Join 的具体含义 Fork 一词的原始含义是吃饭用的叉子,也有分叉的意思.在Linux 平台中,函数 fork()用来创建子进程,使得系统进程可以多一个执行分支.在 Java ...