首先,在谈到Manacher算法之前,我们先来看一个小问题:给定一个字符串S,求该字符串的最长回文子串的长度.对于该问题的求解。网上解法颇多。时间复杂度也不尽同样,这里列述几种常见的解法.

解法一

      通过枚举S的子串。然后推断该子串是否为回文。因为S的子串个数大约为

latex=\dpi{100}&space;\fn_jvn&space;N^2">

\dpi{100}&space;\fn_jvn&space;N^2" title="N^2" alt="">。加上每次推断须要

latex=\dpi{100}&space;\fn_jvn&space;O(n)">的时间,所以总的时间复杂度为,空间复杂度为

\dpi{100}&space;\fn_jvn&space;O(1)" title="O(1)" alt="">.

bool check(string &S, int left, int right)
{
while (left < right && S[left] == S[right])
++left, --right;
return left >= right;
}
int solution(string &S)
{
int ans = 0;
for (int i = 0; i < S.size(); ++i)
for (int j = i; j < S.size(); ++j)
if (check(S, i, j))
ans = max(ans, j - i + 1);
return ans;
}

解法二

        我们也能够利用动态规划求解该问题。

现如果得知S[i....j]是S的一个回文子串,那么,我们相同能够得到S[i+1.....j-1]也是S的一个回文字串,换句话说,我们能够通过已知的状态求解出未知状态。现定义dp[i][j]表示S以i为起点,j为终点的子串是否为回文,状态转移方程也非常easy想到:

因为状态存在

latex=\dpi{100}&space;\fn_jvn&space;N^2">种,所以时间复杂度为

latex=\dpi{100}&space;\fn_jvn&space;O(N^2)">。利用滚动数组,我们能够将空间复杂度降为.

int solution(string &S)
{
vector<vector<bool> > dp(2, vector<bool>(S.size(), false));
int ans = 0;
for (int i = S.size() - 1; i >= 0; --i)
{
for (int j = i; j < S.size(); ++j)
{
dp[i & 1][j] = i <= j - 2 ? (S[i] == S[j] && dp[(i + 1) & 1][j - 1]) : S[i] == S[j];
if (dp[i & 1][j])
ans = max(ans, j - i + 1);
}
}
return ans;
}

解法三

         该解法是基于解法一的一种优化。在解法一中,check函数对于以i为起点,j为终点的回文子串,须要推断(j - i + 1) / 2次,但这里面也存在着某个子串不是回文,但也须要推断(j - i + 1) / 2次的情况,比方:aaabaa,aaaabcaaa....为了避免出现这样的情况,我们能够去枚举回文子串的中点。然后以中点为中心,向两边扩展。这样就能避免上述的最坏情况。枚举子串中点时须要分长度为奇数和偶数的情况,详细的能够參考下这两组例子:aabaa,aabb。中点的个数存在

latex=\dpi{100}&space;\fn_jvn&space;N">个,每次以中点为中心向两边扩展最坏须要

latex=\dpi{100}&space;\fn_jvn&space;O(N)">。所以总时间复杂度为

latex=\dpi{100}&space;\fn_jvn&space;O(N^2)">

\dpi{100}&space;\fn_jvn&space;O(N^2)" title="O(N^2)" alt="">,空间复杂度.

int solution(string &S)
{
const int n = S.size();
int ans = 0;
for (int i = 0; i < n; ++i)
{
//for the odd case
for (int j = 0; (i - j >= 0) && (i + j < n) && S[i - j] == S[i + j]; ++j)
ans = max(ans, j << 1 | 1);
//for the even case
for (int j = 0; (i - j >= 0) && (i + 1 + j < n) && S[i - j] == S[i + 1 + j]; ++j)
ans = max(ans, 2 * j + 2);
}
return ans;
}

解法四

            在解法三中,当枚举以i中点的最长回文子串。须要以i为中点,向两边进行扩展,无疑,最坏情况下会退化到

\dpi{100}&space;\fn_jvn&space;O(n)" title="O(n)" alt="">

这里,我们能够通过利用字符串的hash来减少时间复杂度(注:不熟悉字符串hash的朋友,能够參考下这篇博客点击打开链接,整理的非常具体)。

如果当前推断的是以i为中点偶数长度的最长回文,对于随意一个长度k,如果S[i
- k + 1....i]的hash值与S[i + 1.....i + k]的hash值不同,那么以i为中点的最长回文子串的长度必然小于2 * k,因此。能够通过该条件进行二分。这样就能在

latex=\dpi{100}&space;\fn_jvn&space;O(lgn)">的时间范围内找到最优解。因为每次推断的时间复杂度仅仅须要

\dpi{100}&space;\fn_jvn&space;O(lgn)" title="O(lgn)" alt="">,所以该解法的时间复杂度为

latex=\dpi{100}&space;\fn_jvn&space;O(nlgn)">,空间复杂度为

const int BASE = 131, N = 1e+6 + 7;
typedef unsigned long long ULL;
//rec: record forward direction hash value
//rRec:record backward direction hash value
//P: record power of BASE
ULL rec[N], rRec[N], P[N];
int Bin(int len, int end, int rEnd, int __len)
{
int l = 1, r = len;
while (l <= r)
{
int mid = l + (r - l) / 2;
ULL lHash = rec[end] - (end - mid >= 0 ? rec[end - mid] : 0) * P[mid];
ULL rHash = rRec[rEnd] - (rEnd + mid < __len ? rRec[rEnd + mid] : 0) * P[mid];
if (lHash ^ rHash)
r = mid - 1;
else
l = mid + 1;
}
return r;
}
int solution(char *S)
{
const int len = strlen(S);
P[0] = 1ULL;
//calculate power of BASE
for (int i = 1; i < =len; ++i)
P[i] = P[i - 1] * 131;
rec[0] = S[0], rRec[len - 1] = S[len - 1];
//calculate the string <span style="font-family:Microsoft YaHei;">hash </span>value
for (int i = 1, j = len - 2; i < len; ++i, --j)
rec[i] = rec[i - 1] * BASE + S[i], rRec[j] = rRec[j + 1] * BASE + S[j];
int ans = 0;
for (int i = 0; i < len; ++i)
{
int tmp;
//for the even case
tmp = Bin(min(i + 1, len - i - 1), i, i + 1, len);
ans = max(ans, tmp << 1);
//for the odd case
tmp = Bin(min(i, len - i - 1), i - 1, i + 1, len);
ans = max(ans, tmp << 1 | 1);
}
return ans;
}

上述代码有两个地方须要说明一下:1.无符号长整型溢出时,编译器会自己主动取模 2.关于计算P数组。假设是单case,P数组的求解能够放到solution函数中,假设是多case,P数组的求解必须放到外面,由于P数组仅仅用计算一次就能够了.此种解法。能跑过POJ
3974和hdu 3068,感兴趣的朋友能够试试这样的解法.

解法五

          该问题也能够用后缀数组求解,在源字符串末尾加入一个源字符串中未出现过的字符,然后将源字符串的反转串连接在后面,那该问题就转换为在新得到的字符串中求解某两个后缀的LCP。而求解LCP是后缀数组典型的应用。因为后缀数组构造和实现。相比前面简述的几种方法,实现和理解相比之下要困难的多。这里就不做过多解释.

Manacher算法

        前面简述了五种解法,而各种解法的时间复杂度、空间复杂度也不尽同样。这里在介绍一种时间复杂度、空间复杂度均为的算法:Manacher算法。该算法首先对源字符串进行处理,在源字符串的每个字符前后加一个源字符串中未出现过的字符,比如源字符串为:aba,通过预处理后。源串变为:#a#b#a#。对于新得到的字符串,easy得知,该串没有长度为偶数的回文子串,由于串中没有相邻字符是同样的。这样就避免了讨论奇数、偶数的讨论。
         现定义数组P[i] = x。表示以i为中心[i - x...i + x]是最长且回文的,那么就是源字符串的最长回文子串的长度。以字符串abaaba为例:
                                                   S :  #    a    #    b    #    a    #    a     #    b    #     a    #
                                                   P :  0    1    0    3    0     1    6    1     0    3     0     1    0
         通过观察P数组,发现其最大值是6,而源串中的最长回文子串abaaba的长度也正好是6。如今。面临的问题是怎么求解P数组?
         如果计算P[i]时。P[0..i - 1]已经计算好了。对于前面的P[x](0 <= x < i)。定义一个框[x - P[x]...x + P[x]],right等于max{x + P[x]}。center值为取到right时的x值。如今要计算P[i],对于i值,这里要分两种情况:
  1. i <= right:  先计算i关于center的对称点i' = 2 * center - i,依据回文串的对称性,从框左边left...i'和i...right是一致的。假设P[i']的值能把i + P[i']的值限定在框里,那么P[i] = P[i'],由于框里的东西已经比較过了。比如源串为babcbabcbaccba,如今要计算P[13]值,例如以下图所看到的: 
                                                                 

    i = 13关于center的对称点是i' = 9。将[i' - P[i']......i + P[i]]子串取出(这里为了便于叙述,先如果i' - P[i'] >L)。得到例如以下的图:  
              通过对照上图能够发现,以i'为中点的最长回文子串S[8..10]相应着S[12...14],也就是说,S[i' - P[i']...i' + P[i']]与S[i - P[i']...i + P[i']]一定是相等的(注:此处的前提条件是i' - P[i'] >L),并且P[i]一定等于P[i'],由于S[i + P[i'] + 1] 一定不等于S[i - P[i'] - 1],这在求P[i']时,就已经比較过了。当i' - P[i'] <= L时,能够得到S[L...2 * i' -
    L]一定是回文子串,而S[L...2 * i' - L]恒等于S[2 * i - R....R]。此时,P[i]的值至少是R - i。而大于right部分的,都是没有比較过的,所以仅仅能以i为中点,以R - i + 1为半径向两边扩展。结合i' - P[i'] > L和i' - P[i'] <= L的情况,能够发现P[i]的值至少等于min(P[i'], R - i),所以,在i < right的情况下。使P[i] = min(P[i'], R - i),然后以i为中心,P[i]为半径,向两边扩展。并更新对应的center和right值就可以.

  2. i > right: 这样的情况下,仅仅能以i为中心,向两边扩展,并更新对应的center和right值。

复杂度分析

          计算过程中,须要用到额外的P数组,而right的值仅仅能添加n次,所以该算法的时间、空间复杂度均为

const int N = 1e+6 + 7;
char orign[N << 1];
int P[N << 1];
int Manacher(char *S)
{
int len = strlen(S);
S[len << 1] = '#', S[len << 1 | 1] = '\0';
for (int i = len - 1; i >= 0; --i)
S[i << 1 | 1] = S[i], S[i << 1] = '#';
int center = 0, right = 0, ans = 0;
len <<= 1;
for (int i = 0; i <= len; ++i)
{
P[i] = i <= right ? min(P[2 * center - i], right - i) : 0;
while (i - P[i] - 1 >= 0 && i + P[i] + 1 <= len && S[i - P[i] - 1] == S[i + P[i] + 1])
++P[i];
if (i + P[i] > right)
right = i + P[i], center = i;
ans = max(ans, P[i]);
}
return ans;
}
           前面已经讲述了Manacher算法的工作原理。这里谈一下Manacher算法的应用.

应用一:回文子串个数

         对于一个给定串S,是否能在线性时间复杂度内求出该字符串有多少个回文子串?
        答案是肯定的,统计存在多少个回文子串。仅仅用统计以i(0 <= i < len(S))为中心、长度为偶数的回文子串数量和以i为中心长度为奇数的回文子串数量。然后累加就可以。

如果如今须要求解以i为中心、长度为奇数的回文子串数量。仅仅须要找到以i为中心、长度为奇数的最长回文子串的长度值,然后将长度值加一除2。即为所求的解。偶数的处理方式一样。而在求解最长回文子串的长度时,计算出来的P[i]值。就已经计算出了以源串全部点为中心、长度各自是偶数和奇数的最长回文子串的长度。仅仅须要线性遍历一遍P[i]数组,将(P[i]
+ 1) / 2的值累加,就是S的回文子串的个数。

应用二:扩展KMP

         给定一个串S[0...n],是否能在线性时间复杂度内求出P[i] = LCP(S[i...n],S[0...n])(1 <= i <= n)?
         对于该问题,我们能够套用Manacher算法。

现计算P[i],如果P[1...i - 1]都已经计算好了,设定right为max(P[x] - 1 + x)(1 <= x < i)。left为取到right值时的x值。(1)当right >= i时,通过已经计算出来的P[1..i-1]值,我们可知S[left....right] = S[0...right
- left]。找到i的位置相当于S串的开头的位置:i' = i - left,假设i + P[i'] <= right。那么非常easy得出P[i] = P[i']。假设i + P[i'] > right,那么P[i]的值至少为right - i + 1,综上两个情况,易知P[i]值至少为min(P[i']。right - i + 1),然后暴力比較,并更新对应的left和right。因为right的值仅仅能添加n次。所以该算法是

        当然了,上述的算法并不局限于与串自身匹配LCP。比方给定两个串S、T,要在S串中查找是否出现T串,用一个在S、T中都没有出现的字符连接T和S,这里如果为'#'。得到新串T#S,最后在推断P数组中是否存在值为len(T)的元素就能推断T串是否在S串中出现。而在扩展KMP中。对于模式串T,须要计算LCP(S[i...n],T)(0 <= i <=
n)。相同能够利用上述的方法在的时间范围内求解,这也是Manacher算法与扩展KMP算法的相似之处。

浅谈Manacher算法与扩展KMP之间的联系的更多相关文章

  1. 【字符串算法2】浅谈Manacher算法

    [字符串算法1] 字符串Hash(优雅的暴力) [字符串算法2]Manacher算法 [字符串算法3]KMP算法 这里将讲述  字符串算法2:Manacher算法 问题:给出字符串S(限制见后)求出最 ...

  2. 浅谈Manacher算法

    Manacher manacher是一种\(O(n)\)求最长回文子串的算法,俗称马拉车(滑稽) 直接步入正题 首先可以知道的是:每一个回文串都有自己的对称中心,相应的也有自己的最大延伸长度(可以称之 ...

  3. 浅谈分词算法(4)基于字的分词方法(CRF)

    目录 前言 目录 条件随机场(conditional random field CRF) 核心点 线性链条件随机场 简化形式 CRF分词 CRF VS HMM 代码实现 训练代码 实验结果 参考文献 ...

  4. 浅谈分词算法(5)基于字的分词方法(bi-LSTM)

    目录 前言 目录 循环神经网络 基于LSTM的分词 Embedding 数据预处理 模型 如何添加用户词典 前言 很早便规划的浅谈分词算法,总共分为了五个部分,想聊聊自己在各种场景中使用到的分词方法做 ...

  5. 浅谈分词算法(3)基于字的分词方法(HMM)

    目录 前言 目录 隐马尔可夫模型(Hidden Markov Model,HMM) HMM分词 两个假设 Viterbi算法 代码实现 实现效果 完整代码 参考文献 前言 在浅谈分词算法(1)分词中的 ...

  6. 浅谈分词算法基于字的分词方法(HMM)

    前言 在浅谈分词算法(1)分词中的基本问题我们讨论过基于词典的分词和基于字的分词两大类,在浅谈分词算法(2)基于词典的分词方法文中我们利用n-gram实现了基于词典的分词方法.在(1)中,我们也讨论了 ...

  7. 浅谈Manacher

    \(Manacher\)是由一个叫做\(Manacher\)的人发明的能在\(O(n)\)时间内找出一个字符串长度最长的回文子串的算法. 由于偶回文串形如\(abba\)这样的不好找对称中心,所以我们 ...

  8. 浅谈Tarjan算法

    从这里开始 预备知识 两个数组 Tarjan 算法的应用 求割点和割边 求点-双连通分量 求边-双连通分量 求强连通分量 预备知识 设无向图$G_{0} = (V_{0}, E_{0})$,其中$V_ ...

  9. 浅谈聚类算法(K-means)

    聚类算法(K-means)目的是将n个对象根据它们各自属性分成k个不同的簇,使得簇内各个对象的相似度尽可能高,而各簇之间的相似度尽量小. 而如何评测相似度呢,采用的准则函数是误差平方和(因此也叫K-均 ...

随机推荐

  1. 触发TreeView的TreeNodeCheckChanged事件

    这个事件不会主动postback,需要手动写javascript触发.对网上找到的方法做了些改进,增加UpdatePanel,以免页面不停的刷.这里就不考虑性能神马的了,因为既然项目已经允许选择使用T ...

  2. 用DOM实现文章采集-HtmlAgilityPack实现html解析

    Html Agility Pack 是CodePlex 上的一个开源项目.它提供了标准的DOM API 和XPath 支持! 下载地址:http://htmlagilitypack.codeplex. ...

  3. 用java写bp神经网络(一)

    根据前篇博文<神经网络之后向传播算法>,现在用java实现一个bp神经网络.矩阵运算采用jblas库,然后逐渐增加功能,支持并行计算,然后支持输入向量调整,最后支持L-BFGS学习算法. ...

  4. 使用Eclipse创建Hibernate工程

    创建一个java project项目,加入hibernate的jar包和数据库驱动包,并引入到项目.

  5. CSS3重要内容翻译

    以上是废话 1.3    此处未完全确认,相较于css3和css3的选择器,区别包括: 基础定义改变(选择器.选择器组,简单选择器等),特别的,作为css2中简单选择器,如今被成为简单选择器序列,“简 ...

  6. jquery mobile 栅格化

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  7. Android 多线程:使用Thread和Handler

    当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分 ...

  8. C++ 性能剖析 (一)

    C++ 性能剖析 (一) 性能问题也不是仅仅用“技术”可以解决的,它往往是架构,测试,假设等综合难题.不过,对于一个工程师来说,必须从小做起,把一些“明显”的小问题解决.否则的话积小成多,千里堤坝,溃 ...

  9. Nginx源码研究七:nginx的location指令分析

    在nginx的配置文件nginx.conf中,我们在配置server的时候,会配置一下location指令,这个location指令是提供给用户来配置对于符合指令的http请求,采用该指令内部的处理方 ...

  10. hdu 1241 Oil Deposits(DFS求连通块)

    HDU 1241  Oil Deposits L -DFS Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & ...