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

简介: 求最长回文串的利器 - Manacher算法 Manacher主要是用来求某个字符串的最长回文子串. 不要被manacher这个名字吓倒了,其实manacher算法很简单,也很容易理解,程序短,时间复杂度为O(n). 求最长回文子串这个问题,我听说有个分治+拓展kmp的算法,后缀数组也可以. 但是对于求回文串来说,manacher算法肯定有很多其他算法没有的优点。

 

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

Manacher主要是用来求某个字符串的最长回文子串.

不要被manacher这个名字吓倒了,其实manacher算法很简单,也很容易理解,程序短,时间复杂度为O(n).

求最长回文子串这个问题,我听说有个分治+拓展kmp的算法,后缀数组也可以.

但是对于求回文串来说,manacher算法肯定有很多其他算法没有的优点。

现在进入正题:

首先,在字符串s中,用rad[i]表示第i个字符的回文半径,即rad[i]尽可能大,且满足:

s[i-rad[i],i-1]=s[i+1,i+rad[i]]

很明显,求出了所有的rad,就求出了所有的长度为奇数的回文子串.

至于偶数的怎么求,最后再讲.

假设现在求出了rad[1..i-1],现在要求后面的rad值,并且通过前面的操作,得知了当前字符i的rad值至少为j.

现在通过试图扩大j来扫描,求出了rad[i].再假设现在有个指针k,从1循环到rad[i],试图通过某些手段来求出[i+1,i+rad[i]]的rad值.

根据定义,黑色的部分是一个回文子串,两段红色的区间全等.

因为之前已经求出了rad[i-k],所以直接用它.有3种情况:

①rad[i]-k<rad[i-k]
如图,rad[i-k]的范围为青色.因为黑色的部分是回文的,且青色的部分超过了黑色的部分,所以rad[i+k]肯定至少为rad[i]-k,即橙色的部分.

那橙色以外的部分就不是了吗?这是肯定的.因为如果橙色以外的部分也是回文的,那么根据青色和红色部分的关系,可以证明黑色部分再往外延伸一点也是一个回文子串,这肯定不可能,因此rad[i+k]=rad[i]-k.为了方便下文,这里的rad[i+k]=rad[i]-k=min(rad[i]-k,rad[i-k]).


②rad[i]-k>rad[i-k]
如图,rad[i-k]的范围为青色.因为黑色的部分是回文的,且青色的部分在黑色的部分里面,根据定义,很容易得出:rad[i+k]=rad[i-k].为了方便下文,这里的rad[i+k]=rad[i-k]=min(rad[i]-k,rad[i-k]).

根据上面两种情况,可以得出结论:当rad[i]-k!=rad[i-k]的时候,rad[i+k]=min(rad[i]-k,rad[i-k]).
注意:当rad[i]-k==rad[i-k]的时候,就不同了,这是第三种情况:

如图,通过和第一种情况对比之后会发现,因为青色的部分没有超出黑色的部分,所以即使橙色的部分全等,也无法像第一种情况一样引出矛盾,因此橙色的部分是有可能全等的,但是,根据已知的信息,我们不知道橙色的部分是多长,因此就把i指针移到i+k的位置,j=rad[i-k](因为它的rad值至少为rad[i-k]),等下次循环的时候再做了.
整个算法就这样.
至于时间复杂度为什么是O(n),我已经证明了,但很难说清楚.所以自己体会吧.
上文还留有一个问题,就是这样只能算出奇数长度的回文子串,偶数的就不行.怎么办呢?有一种直接但比较笨的方法,就是做两遍(因为两个程序是差不多的,只是rad值的意义和一些下标变了而已).但是写两个差不多的程序是很痛苦的,而且容易错.所以一种比较好的方法就是在原来的串中每两个字符之间加入一个特殊字符,再做.如:aabbaca,把它变成a#a#b#b#a#c#a,这样的话,无论原来的回文子串长度是偶数还是奇数,现在都变成奇数了.

模板题:HDU 3068

/*
* this code is made by crazyacking
* Verdict: Accepted
* Submission Date: 2015-08-19-15.59
* Time: 0MS
* Memory: 137KB
*/
#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#define maxx 20000050
#define  LL long long
#define  ULL unsigned long long
using namespace std;
char str [ 2 * maxx ];
char s [ maxx ];
int p [ maxx ];
void Manacher( int *p , char * str , int len)
{
      int mx = 0;
      int idx = 0;
      for( int i = 1; i < len; i ++)
      {
           p [ i ] = mx > i ? min(p [ 2 * idx - i ], mx - i) : 1;
            while( str [ i +p [ i ]] == str [ i -p [ i ]])
                 p [ i ] ++;
            if( i +p [ i ] > mx)
            {
                  mx = i +p [ i ];
                  idx = i;
            }
      }
}
int main()
{
      while( scanf( "%s" ,s) != EOF)
      {
            int len = strlen(s);
            int n = 2 * len + 2;
            str [ 0 ] = '$';
            for( int i = 0; i <= len; i ++)
            {
                  str [ 2 * i + 1 ] = '#';
                  str [ 2 * i + 2 ] =s [ i ];
            }
            Manacher(p , str ,n);
            int ans = 1;
            for( int i = 0; i <n; i ++)
                  ans = max( ans ,p [ i ]);
            printf( "%d \n " , ans - 1);
      }
      return 0;
}

 

目录
相关文章
|
1月前
|
并行计算 算法 测试技术
模拟算法题练习(一)(扫雷,灌溉,回文日期)
模拟算法题练习(一)(扫雷,灌溉,回文日期)
|
6月前
|
算法 测试技术 C++
C++算法:最短回文串
C++算法:最短回文串
|
3月前
|
算法
算法题解-回文链表
算法题解-回文链表
|
3月前
|
算法 Java C++
数据结构与算法面试题:实现一个函数,判断一个链表是否为回文链表。(提示:反转后半部分链表比对前半部分)
数据结构与算法面试题:实现一个函数,判断一个链表是否为回文链表。(提示:反转后半部分链表比对前半部分)
21 0
|
4月前
|
算法 C语言
【牛客-算法】NC56 回文数字
🚩 前言 🔥 该专栏作为算法题笔记,记录算法的思路、遇到的问题,以及能跑的代码,持续更新中! 🔥 推荐一款面试、刷题神器牛客网:👉开始刷题学习👈
31 0
|
4月前
|
算法 vr&ar 图形学
☆打卡算法☆LeetCode 214. 最短回文串 算法解析
☆打卡算法☆LeetCode 214. 最短回文串 算法解析
|
4月前
|
算法 程序员
【算法训练-链表 三】【特殊链表】环形链表、环形链表II、回文链表、相交链表
【算法训练-链表 三】【特殊链表】环形链表、环形链表II、回文链表、相交链表
37 0
|
4月前
|
算法 测试技术 C#
C++深度优先搜索(DFS)算法的应用:树中可以形成回文的路径数
C++深度优先搜索(DFS)算法的应用:树中可以形成回文的路径数
|
9月前
|
存储 算法
Manacher算法解析
Manacher算法解析
57 0
|
10月前
|
算法 C语言 C++
【基础算法】单链表的OJ练习(4) # 分割链表 # 回文链表 #
【基础算法】单链表的OJ练习(4) # 分割链表 # 回文链表 #