最长回文子串

问题定义

最长回文子串问题:给定一个字符串,求它的最长回文子串长度。

解法1:暴力解法

找到字符串的所有子串,判断每一个子串是否是回文串。
一个子串有该串的起点和终点确定,因此对于长度为N的字符串共有 N2个子串, 这些子串的平均长度为N/2 (判断是否是回文串的时间复杂度O(N)). 因此时间复杂度O(N3)

解法2:改进暴力解法

所有的回文串都是关于某个位置对称。
长度为奇数回文串以最中间字符的位置为对称轴左右对称;
长度为偶数的回文串以中间两个字符之间的空隙为对称轴左右对称。

我们可以遍历字符串中的这些位置, 从每个位置同时向左右扩展,直到两边的字符不同,或达到边界。这类位置共 N + N-1 = 2N-1个,且在每个位置上大约要进行N/4次字符比较,因此算法复杂度O(N2)

解法3:Mancher算法

解法2存在的缺陷:

  1. 存在很多子串被多次重复访问比较的情况
  2. 回文串的长度的奇偶性,造成不同的对称轴位置,解法2需要分别处理

步骤1:解决因回文串的长度的奇偶性需要分别处理对称轴的问题

方法:在字符串的开头末尾,以及每两个字符的中间位置插入唯一标识符#.
这样构造字符串后,字符串的长度始终是奇数。 例如,

aba ==> #a#b#a#
abba ==> #a#b#b#a#

步骤2:解决子串多次重复访问的问题

为了最大程度的利用已经访问过的回文串的信息, Manacher算法巧妙的定义了回文半径: 即,回文串最左或最右位置到对称轴的距离。
回文半径数组RL, RL[i]表示以第 i 个字符为对称轴的回文串的回文半径。例如,

Paste_Image.png

RL[i]值的性质:RL[i]-1 表示 原始字符串中以 第 i 个位置为对称轴 的最长回文串长度。
证明
在改造过的字符串中,以 第 i 个位置为对称轴的回文串的 最左和最右字符一定是 #.
第i个位置对应的字符,分两种情况,(如图1):

  1. 第i个位置对应的字符是 #,则回文串共有奇数个字符,从回文串的最左位置到第i个位置共有,(RL[i]-1)/2 个非#字符, (RL[i] - (RL[i]-1)/2)个#字符, 由于左右关于第 i 个位置对称,因此,该回文串中非#字符共有 (2 * (RL[i]-1)/2) = (RL[i]-1)个非#字符。
  2. 第i个位置对应的字符是 非#字符,则回文串共有偶数个字符,从回文串的最左位置到第i个位置共有,RL[i]/2-1 个非#字符 (减1是为了不计算第 i 个位置的字符), (RL[i] - RL[i]/2+1)个#字符, 由于左右关于第 i 个位置对称,因此,该回文串中非#字符共有 (2 * (RL[i]/2-1)) + 1 = (RL[i] - 1)个非#字符 (最后的+1,是将第 i 个位置也算上)。
图1

步骤3:如何利用RL数组,减少重复访问字符串

为了尽可能的减少重复访问字符串的次数,引入变量 MaxRigth 表示 在从左到右,已经访问过的回文串中,回文串所能触及到字符串的最右位置,即该回文串的中心为pos,则其关系如下图2所示:

图2

idx在4和12之间的所有字符都关于pos位置对称!

由于pos是已经访问过的位置,则 当前访问到的位置 i 只能位于pos的右边,且有两种情况:

1)当前访问的位置 i 在MaxRight的左边,如图3所示

图3

从图中可以看出,以位置 i 为中心的回文串必然与以pos为中心的回文串存在一部分的重合。

现在我们想找出以位置 i 为中心的回文字符,为了减少重复访问字符,我们希望可以知道以位置 i 为中心的左右两边哪些字符已经是对称的。

我们知道,以pos为中心的左右两边对称,位置 i 在pos的右边,那么在pos的左边必然存在和 位置 i 对称的位置(假设我们记该对称的位置为 j)。如图3中的idx=6 。

由于位置 j 已经访问过,我们知道以位置 j 为中心的回文串回文半径, 此处分两种情况讨论:

  1. 以位置 j 为中心的回文串 位置pos和位置Maxright的对称位置之间,如图4所示.
图4

如图所示,由于以位置 j 为对称轴的回文串的回文半径已知,根据对称性,我们知道位置 i 的左右邻居对称,因此可以从左右邻居开始寻找以位置 i 为对称抽的回文串,这样便减少了对字符的重复访问。

  1. 以位置 j 为中心的回文串不在 位置pos和Maxright的对称位置之间,如图5所示.
图5

此时我们只能确定红色线条之间字符关于位置 i 对称,但这也减少了重复访问字符的次数。此时,只需要从左红线的左端,右红线的右端开始遍历字符、判断对称,寻找最长回文字符。

2)当前访问的位置 i 在 MaxRight的右边,如图6。

图6

此时,说明以位置 i 为对称轴的回文串的左右两侧的对称信息,无法从历史信息中推导出来,我们不得不从位置 i 的左右邻居开始判断是否相同,指定遇到不同的字符或达到边界为止。

步骤4:如何更新RL数组, MaxRight变量,位置pos

if(i < MaxRight){
    // RL[i] 初始值
    RL[i] = min(RL[pos - (i -  pos], MaxRight-i)
}else {
    // RL[i] 初始值
    RL[i] = 1;
}

以位置 i 为对称轴 从对称轴的左右RL[i]距离处,同时向左右开始访问字符,并同时更新RL[i]

MaxRight = RL[i]+i-1 > MaxRight ? RL[i]+i-1 : MaxRight

pos =  RL[i]+i-1 > MaxRight ? i : pos;

算法实现

public int findLongestPalindromicSubstring(String s){
        // 填充字符, 假设字符#在s中没有出现过
        String cs = "#";
        for(int i=0; i<s.length(); ++i){
            cs += s.charAt(i);
            cs += "#";
        }

        // 保存最长的回文字符串的长度
        int maxlen = 0;
        int[] RL = new int[cs.length()];
        int maxRight=0, pos=0;

        for(int i=0; i<cs.length(); ++i){
           // 根据 i 位于maxRight的左边还是右边更新RL[i]
            if(i < maxRight){
                // i 在maxRight左边的情况
                RL[i] = Math.min(RL[2*pos-1], maxRight-i);
            }else{
                // i 在maxRight右边的情况
                RL[i]=1;
            }
            // 边界判断, 回文判断
            while(i+RL[i]<cs.length() && i-RL[i] >=0 && cs.charAt(i+RL[i])==cs.charAt(i-RL[i])){
                ++RL[i];
            }

            if(RL[i]+i-1 > maxRight){
                maxRight = RL[i] + i -1;
                pos = i;
            }

            // 更新最长回文字符串的长度
            maxlen = maxlen > RL[i] ? RL[i] : maxlen;
        }
        // 利用RL的性质
        return maxlen-1;
    }

复杂度分析

空间复杂度:插入分隔符形成新串,占用了线性的空间大小;RL数组也占用线性大小的空间,因此空间复杂度是线性的。
时间复杂度:尽管代码里面有两层循环,通过平摊分析,我们可以得出,Manacher的时间复杂度是线性的。由于内层的循环只对尚未匹配的部分进行,因此对于每一个字符而言,只会进行一次,因此时间复杂度是O(n)。

注:文本主要参考了文献[1],并在理解的基础上,做了些许更改。

参考文献

[1] https://segmentfault.com/a/1190000003914228

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容