算法进阶之manacher算法 (求最长回文)

前几天bestcode做到一道字符串的题目,需要O(n)的回文,正好看到网上的manacher算法,于是来学习一发

进入正题:

manacher算法

用法:一般用于求一个字符串的最大回文,操作过程中会记录以每个点为中心的回文半径,可用来进行其他操作

时间复杂度:O(n)

空间复杂度:记录字符串2*n,半径数组2*n

内容:

记p[i]为以i为中心的回文半径(aba中以b为中心的回文半径为2,那么如果是偶数个数的回文呢,比如abba?这里先讨论奇数,偶数后面再说,简单说就是用字符填充变成奇数)

因此以下的部分讨论的都是奇数个数的回文>>>>>>>>>>>>>>

那么无可避免先要for一遍,for(j=0;j<n;i++) 来求每个p[j],下面就是对p[j](令j=i+k)的求解

分析:假设已经求出了i+k前面的0~i+k-1的p,现在要求p[i+k],

那么首先根据前面的点求半径的时候有没有遍历到了i+k这个点可分为两大类:

一、 前面的点求半径的时候还没有遍历到了i+k这个点,即p[i]+i<i+k

(设i为前i+k个点中半径时遍历到最靠近字符串后面的点,不是回文最长!)

那么此时p[i+k]=1;

这时候说明此时的i+k还没找过半径,需要从头开始找while(s[i+k+p[i+k]]==s[i+k-p[i+k]]) p[i+k]++;

二、 前面的点求半径时已经遍历到了i+k这个点,即p[i]+i>=i+k

(设i为前i+k个点中半径时遍历到最靠近字符串后面的点,不是回文最长!)

这时候i+k前面有关于i对称的i-k,所以算i+k的回文半径时不需要从第一个开始找,那么是从p[i-k]个开始找吗?

答案是不一定的!

下面借用网上的图片来分析一下可能出现的两种情况:

<1>p[i]-k<p[i-k](此时p[i-k]>=p[i+k]),下面是结合图片的分析

首先解释一下图,图中的红线为i的回文半径,黑线为i求回文半径时遍历过的点,深蓝线是i-k的回文半径,那么此时i+k的回文半径一定是橙线,为什么呢?

首先橙线部分肯定是i+k的半径的一部分,由于对称性可以得到与左边i-k的橙线部分相同,

那么i+k的半径可能大于橙线部分吗?

假设i+k的回文半径大于橙线,如下图中橙线+紫线,那么由于对称性i-k也必定含有紫线部分,那么此时p[i]就不再是原来的p[i]了,而是如下图的黑线+紫线,这个与之前矛盾,所以不可能存在

即p[i+k]=p[i]-k;

<2>p[i]-k>=p[i-k](此时p[i-k]<=p[i+k]),下面是结合图片的分析

图中的红线为i的回文半径,黑线为i求回文半径时遍历过的点,深蓝线是i-k的回文半径,首先由于对称性i+k的回文半径一定包含蓝线部分,与此同时i+k的右边可能还有字符能组成更长的回文,最后形成图中的橙线部分,因此此时P[i+k]>=p[i-k],因此可以先赋值p[i+k]=p[i-k],然后while(s[i+k+p[i+k]]==s[i+k-p[i+k]])
p[i+k]++;继续往后面找。

于是由上可以总结出p[j]的求法:

void query_p(int n)                             //n为字符串的长度
{
    int id = 0;                                 //id为上述i
    p[0] = 1;
    for(int j=1;j<n;j++)                        //j为上述i+k,所以k=j-id
    {
        if(p[id]+id<=j) p[j]=1;                 //第一种情况
        else p[j]=min(p[2*id-j],p[id]-(j-id));  //第二种情况的两种小情况
        while(s[j+p[j]]==s[j-p[j]]) p[j]++;     //继续往后找
        if(p[j]+j>p[id]+id) id=j;               //更新最靠近字符串后面的点
    }
}

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<到这里为止都是把回文当做奇数个来算的,那么如果出现偶数个怎么办呢,具体做法是利用字符填充,字符可以用字符串中肯定不会出现的字符来填充,具体做法是:

假设字符串中只有字母,那么可以利用特殊符号填充,填充方法是在每个字符的前面插入一个一样的字符

比如填充字符时#时,原字符串是abba,填充后的字符变为#a#b#b#a#,然后就可以了,至于字母的原始回文半径就是现在的p[i]/2+1,填充字符不会影响结果。

不过这里需要注意的是:为了避免数组越界需要在填充后在字符再处理一下如上述*#a#b#b#a#‘\0‘,这样从第一个字母开始遍历的时候就不会出现问题了

这里贴一个填充后的求最长回文的代码:

int for_max(int n)                             //n为原字符串的长度
{
    for(int j=n;j>=0;j--)                       //字符填充
    {
        s[j*2+2]=s[j];
        s[j*2+1]='#';
    }
    s[0]='$';                                   //防止数组越界填充第一个字符和最后一个字符为‘$’和‘/0’
    int id = 0;                                 //id为上述i
    int maxlen = 0;
    p[0] = 1;
    for(int j=2;j<2*n+2;j++)                        //j为上述i+k,所以k=j-id
    {
        if(p[id]+id<=j) p[j]=1;                 //第一种情况
        else p[j]=min(p[2*id-j],p[id]-(j-id));  //第二种情况的两种小情况
        while(s[j+p[j]]==s[j-p[j]]) p[j]++;     //继续往后找
        if(p[j]+j>p[id]+id) id=j;               //更新最靠近字符串后面的点
        maxlen=max(maxlen,p[j]-1);              //更新最长回文
    }
    return maxlen;
}

具体题目可以看HDU3068:题意大概为给一个字符串,求最长回是多少

http://acm.hdu.edu.cn/showproblem.php?pid=3068

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include<vector>
#pragma comment(linker,"/STACK:1024000000,1024000000")
using namespace std;
const int maxn = 2.2e5+5;
char s[maxn*2];                                 //2倍数组
int p[maxn*2];
int for_max(int n)                             //n为原字符串的长度
{
    for(int j=n;j>=0;j--)                       //字符填充
    {
        s[j*2+2]=s[j];
        s[j*2+1]='#';
    }
    s[0]='$';                                   //防止数组越界填充第一个字符和最后一个字符为‘$’和‘/0’
    int id = 0;                                 //id为上述i
    int maxlen = 0;
    p[0] = 1;
    for(int j=2;j<2*n+2;j++)                        //j为上述i+k,所以k=j-id
    {
        if(p[id]+id<=j) p[j]=1;                 //第一种情况
        else p[j]=min(p[2*id-j],p[id]-(j-id));  //第二种情况的两种小情况
        while(s[j+p[j]]==s[j-p[j]]) p[j]++;     //继续往后找
        if(p[j]+j>p[id]+id) id=j;               //更新最靠近字符串后面的点
        maxlen=max(maxlen,p[j]-1);              //更新最长回文
    }
    return maxlen;
}
int main()
{
    int n;
    while(scanf("%s",s)!=EOF){
        int m=strlen(s);
        printf("%d\n",for_max(m));
    }
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 23:53:02

算法进阶之manacher算法 (求最长回文)的相关文章

O(n) 求最长回文子串的 Manacher 算法

Manacher是一个可以在O(n)的时间内求出一个长度为n的字符串的算法. 以为回文子串有偶数长度,也有奇数长度,分别处理会很不方便. 所以在每两个字符中间插入一个无关字符,如‘#’,这样所有的回文子串都变为奇数长度. 两端在添加不同的无关字符防止匹配时越界. 如: abba 变成 $#a#b#b#a#& 预处理代码: void Prepare() { l = strlen(Str); S[0] = '$'; for (int i = 0; i <= l - 1; i++) { S[(i

[hdu3068 最长回文]Manacher算法,O(N)求最长回文子串

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3068 题意:求一个字符串的最长回文子串 思路: 枚举子串的两个端点,根据回文串的定义来判断其是否是回文串并更新答案,复杂度O(N3). 枚举回文串的对称轴i,以及回文半径r,由i和r可确定一个子串,然后暴力判断即可.复杂度O(N2). 在上一步的基础上,改进判断子串是否是回文串的算法.记fi(r)=(bool)以i为对称轴半径为r的子串是回文串,fi(r)的值域为{0, 1},显然fi(r)是关于r

Manacher&#39;s algorithm: 最长回文子串算法

Manacher 算法是时间.空间复杂度都为 O(n) 的解决 Longest palindromic substring(最长回文子串)的算法.回文串是中心对称的串,比如 'abcba'.'abccba'.那么最长回文子串顾名思义,就是求一个序列中的子串中,最长的回文串.本文最后用 Python 实现算法,为了方便理解,文中出现的数学式也采用 py 的记法. 在 leetcode 上用时间复杂度 O(n**2).空间复杂度 O(1) 的算法做完这道题之后,搜了一下发现有 O(n) 的算法.可惜

JavaScript算法----给定一个长度为N的串,求最长回文子串。

/* *给定一个长度为N的串,求最长回文子串. */ function returnStr(str){ console.log(str); var arr = [],s = ""; for(var i=0;i<str.length;i++){ s = ""; if(str.charAt(i)==str.charAt(i+1)){ var j=0; while(str.charAt(i+j+1)==str.charAt(i-j)){ s = str.charAt

HDU 4513 吉哥系列故事——完美队形II manacher求最长回文

题目来源:吉哥系列故事--完美队形II 题意:中文 思路:在manacher算法向两边扩展的时候加判断 保证非严格递减就行了 #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100110; int a[maxn<<1]; int b[maxn<<1]; int dp[maxn<<1]; int

Manacher求最长回文

#1032 : 最长回文子串 时间限制:1000ms 单点时限:1000ms 内存限制:64MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:"小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?" 小Ho奇怪的问道:"什么叫做最长回文子串呢?" 小Hi回答道:"一个字符串中连续的一

Manacher HDOJ 3068 最长回文

题目传送门 关于求解最长回文子串,有dp做法,也有同样n^2的但只用O(1)的空间,还有KMP,后缀数组?? 1 int main(void) { 2 while (scanf ("%s", str + 1) == 1) { 3 int len = strlen (str + 1); 4 memset (dp, false, sizeof (dp)); 5 for (int i=1; i<=len; ++i) dp[i][i] = true; 6 for (int i=1; i&

后缀数组 - 求最长回文子串 + 模板题 --- ural 1297

1297. Palindrome Time Limit: 1.0 secondMemory Limit: 16 MB The “U.S. Robots” HQ has just received a rather alarming anonymous letter. It states that the agent from the competing «Robots Unlimited» has infiltrated into “U.S. Robotics”. «U.S. Robots» s

求最长回文子串,O(n)复杂度

最长回文子串问题-Manacher算法 最长回文串问题是一个经典的算法题. 0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.下面是一些回文串的实例: 12321 a aba abba aaaa tattarrattat(牛津英语词典中最长的回文单词) 1. Brute-force解法 对于最长回文子串问题,最简单粗暴的办法是:找到字符串的所有子串,遍历每一个子串以验证它们是否为回文串.一个子串由子串的起点和终点确定