要解决的问题

求一个字符串最长回文子串是什么。且时间复杂度 O(N)

具体描述可参考:

LeetCode_5_最长回文子串

LintCode_200_最长回文子串

暴力解法

以每个字符为中心向左右两边扩,直到扩不动为止,记录下每个字符对应能扩的范围大小。因为有每个位置左右两边能扩的最大范围,我们可以很方便还原出最长回文子串是什么。

比如:AB1234321CD 这个字符串,以4字符为中心向左右两边能扩的位置最大,1234321 为最长回文子串。

如上解法有个问题,即针对类似1ABBA2这样的字符串,如上算法会错过最长回文子串ABBA, 因为ABBA不是以任何一个字符串向左右两边扩散得到的。所以,需要预处理一下原始字符串,预处理的方式如下:

在字符串的每两个位置之间插入一个特殊字符,变成一个预处理后的字符串,比如我们可以以#作为特殊字符(特殊字符选哪个无所谓,不必非要是原始串中不含有的字符),将1ABBA2这个字符串预处理成1#A#B#B#A#2,用预处理串来跑这个暴力解法,会得到#A#B#B#A#这个是预处理串的最长回文子串,我们可以很方便把这个串还原成原始串的最长回文子串。

暴力解法时间复杂度为O(N^2)

Manacher算法

Manacher算法可以用O(N)时间复杂度解决这个问题。同样的,Manacher算法也需要对原始字符串进行上述的预处理过程。

相关变量说明

pArr

一个整型数组,长度和预处理串一样,存每个位置的最长回文半径是多少。

比如#A#B#B#A#这个字符串,

位于数组2号位置的A的回文半径是A#或者#A, 长度为2,则pArr[2] = 2

位于数组4号位置的#的回文半径是#B#A#或者#A#B#, 长度为5, 则pArr[4] = 5

其他位置以此类推。

通过pArr的定义,我们显然可以得到如下结论

pArr[0] = 1

i

整型,当前遍历到的位置,因为pArr[0]=1, 所以i可以从1开始遍历。

r

整型,回文最右边界,只要某个位置能扩到超过这个位置,就更新r这个值,初始值为0,因为一个字符串回文字符串至少是1,可以以第0个字符为中心且以0为最右边界(即:第0个字符本身作为一个回文串)

c

整型,就是扩到r位置的的中心点,即pArr[c] = r - c + 1,初始值为0,与r的初始值定为0一样的考虑。

流程

考虑i, r, c三个变量之间的位置关系,无非有以下两种情况

情况1. ir外,比如初始状态下:i=1, r,c = 0

情况2. ir内或者i==r

关于情况1,流程如暴力解法一样,以i位置为中心,左右两边扩到不能再扩的位置,更新pArr[i],c, r的值。

关于情况2,我们假设i'i关于c对称的点,r'r关于c对称的点,示例图如下:

细分如下几种情况:

情况2-1

i'自己的回文区域都在[r'...r]内。

例如下图中[6...10]i'的最长回文区域,左边界并未超过r'

由此可以推出,由于i位置和i'位置是关于c位置对称的,则i位置的回文区域至少包括[14...19]这一段,如下图

pArr[i']至少等于pArr[i],接下来考虑i能否继续扩散,即考虑19位置的值是否等于13位置的值,

我们可以假设19位置的值和13位置的值相等,同时,有如下两个显而易见的结论

  1. 19位置的值等于5位置的值。

  2. 13位置的值等于11位置的值。

推出5位置的值和11位置的值相等,那么由于我们前面假设i'只能扩散到最左6位置以及最右10位置,所以,推出的结论和我们的假设矛盾,所以,19位置的值不等于13位置的值

所以情况2-1的结论是:i的最长回文区域长度和i'的答案一样, 即:pArr[i'] = pArr[i]

情况2-2

i'自己的回文区域在[r'...r]

如下图

其中[2...14]范围是以i'为中心的最长回文区域。

在情况2-2下,我们可以得到如下几个结论:

  1. 根据ii'的关系,以i为中心,从[13...19]至少是回文的。

  2. 根据i'的回文区域,12位置的值等于4位置的值,以c为中心,4位置的值又等于20位置的值,所以12位置的值等于20位置的值,即以i为中心,最长回文区域还可以扩展到[12...20]

  3. 根据i'的回文区域,13位置的值等于3位置的值,以c为中心,13位置的值又等于11位置的值,3位置的值等于21位置上的值,所以11位置的值等于21位置的值,即以i为中心,最长回文区域还可以扩展到[11...21]

  4. 继续判断以i为中心,是否可以继续扩散,即要继续判断10位置的值是否等于22位置的值,我们假设10位置的值等于22位置的值,以c为中心,10位置的值等于14位置的值,以i'为中心,14位置的值等于2位置的值,所以10位置的值等于2位置的值,根据我们的假设,2位置的值会等于22位置的值。这个与我们的前提矛盾了,因为我们的前提是c只能扩展到[3...21]这个区域,即:2位置的值不可能等于22位置的值,所以我们的假设不成立,所以10位置的值不等于22位置的值。

所以,情况2-2的结论是:ir的距离就是i的回文半径,即:pArr[i] = r - i + 1

情况2-3

i'自己的回文区域左边界和r'压线

如下图

其中[3...13]区域为以i'为中心能扩的最大回文区域。

有了情况2-2的铺垫,i在情况2-3条件下至少可以扩充的范围是[11...21], 但是接下来是否可以继续扩充,还需要逐个判断。

自此,所有情况考虑完毕。

由于i在遍历过程中,始终不回退,所以,Manacher算法时间复杂度O(N)

完整代码

public class LeetCode_0005_LongestPalindromicSubstring {

    public static String longestPalindrome(String s) {
if (s == null || s.length() <= 1) {
return s;
}
char[] str = s.toCharArray();
char[] strs = manacherStr(str);
int[] pArr = new int[strs.length];
int c = 0;
int r = 0;
int i = 1;
int len = strs.length;
int max = 1;
while (i < len) {
// pArr[i] 至少不需要扩的大小
pArr[i] = i < r ? Math.min(r - i, pArr[c - (i - c)]) : 1;
// 暴力扩
while (i + pArr[i] < len && i - pArr[i] >= 0) {
if (strs[i + pArr[i]] == strs[i - pArr[i]]) {
pArr[i]++;
} else {
break;
}
}
// 扩散的位置能否更新回文有边界R
// 如果可以更新,则更新R,且把C置于当前的i,因为是当前的i让回文右边界扩散的
if (i + pArr[i] > r) {
r = i + pArr[i];
c = i;
}
max = Math.max(pArr[i++], max);
} // 定位最大回文有边界的回文中心是哪个
int n = 0;
for (; n < len; n++) {
if (pArr[n] == max) {
break;
}
} // 构造最大回文子串
StringBuilder sb = new StringBuilder();
for (i = n - max + 2; i < n + max; i += 2) {
sb.append(strs[i]);
}
return sb.toString();
} public static char[] manacherStr(char[] str) {
char[] strs = new char[str.length << 1 | 1];
for (int i = 0; i < strs.length; i++) {
strs[i] = ((i & 1) == 1) ? str[i >> 1] : '#';
}
return strs;
}
}

相关习题

LeetCode_0005_LongestPalindromicSubstring

LintCode_0200_LongestPalindromicSubstring

LeetCode_0647_PalindromicSubstrings

LeetCode_0214_ShortestPalindrome

更多

算法和数据结构笔记

参考资料

使用manacher算法解决最长回文子串问题的更多相关文章

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

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

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

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

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

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

  4. Manacher (马拉车) 算法:解决最长回文子串的利器

    最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...

  5. Manacher's algorithm: 最长回文子串算法

    Manacher 算法是时间.空间复杂度都为 O(n) 的解决 Longest palindromic substring(最长回文子串)的算法.回文串是中心对称的串,比如 'abcba'.'abcc ...

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

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

  7. manacher算法求最长回文子序列

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

  8. 面试经典算法:马拉松算法,最长回文子串Golang实现

    求一个字符串中最长的回文子串. package main import "fmt" /* 马拉松算法,求最长回文子串,时间复杂度:线性 */ func main() { // 回文 ...

  9. manacher 算法(最长回文串)

    manacher算法: 定义数组p[i]表示以i为中心的(包含i这个字符)回文串半径长 将字符串s从前扫到后for(int i=0;i<strlen(s);++i)来计算p[i],则最大的p[i ...

随机推荐

  1. sqli-labs lesson 32-37

    宽字节注入: 原理:mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如%aa%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围).我们在过滤 ' 的时候(也就是从防御的角度 ...

  2. 六种方式,教你在SpringBoot初始化时搞点事情!

    前言 在实际工作中总是需要在项目启动时做一些初始化的操作,比如初始化线程池.提前加载好加密证书....... 那么经典问题来了,这也是面试官经常会问到的一个问题:有哪些手段在Spring Boot 项 ...

  3. linux中文件内核数据结构

    3.文件io 3.1 文件内核数据结构 3.2 复制文件描述符的内核数据结构 3.3 对指定的描述符打印文件标志 #include "apue.h" #include <fc ...

  4. Oracle11 创建表空间、创建角色及导入

    针对日常工作中经常使用命令创建表空间,导入数据,特此记录下(windows环境下),记录中的testSpaceName是表空间名称,testUserName是用户名,userPwd 是用户密码. 1. ...

  5. docker 部署mysql连接问题

    发现windows上有一个docker descktop(虽然不怎么好用), 安装之后准备直接用docker搭本地测试环境的基础设施(比如MySQL,Redis,MongoDB,ES啥的), 虽然比去 ...

  6. The Programmer's Oath程序员的誓言----鲍勃·马丁大叔(Bob Martin)

    In order to defend and preserve the honor of the profession of computer programmers, I Promise that, ...

  7. linux & windows下的动态库制作

     动态链接库不是linux独有的特性,在windows下面也存在这样的特性.一般来说,windows下面的动态连接库是以*.dll作为结尾的,而linux下面的动态连接库是以*.so结尾的.和静态链接 ...

  8. LeetCode42. 接雨水(java)

    42.接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水. 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种 ...

  9. docker PXC MYSQL集群节点启动失败/节点顺序消失/只剩一个节点存在问题的解决

    转载于:https://my.oschina.net/u/4884318/blog/4908669 大牛 佩服此人 截取本人遇到的问题: "error:0407008A:rsa routin ...

  10. Learning ROS: Using a C++ class in Python

    http://wiki.ros.org/ROS/Tutorials/Using%20a%20C%2B%2B%20class%20in%20Python This tutorial illustrate ...