經(jīng)典問(wèn)題與算法:最長(zhǎng)回文子串問(wèn)題與Manacher算法

問(wèn)題描述:給定一個(gè)字符串,求出其最長(zhǎng)回文子串的長(zhǎng)度
例如:對(duì)于字符串s="acaacdbab"而言,其回文子串分別為"caac"和"bab",其中最長(zhǎng)回文子串長(zhǎng)度為4

解法一:中心擴(kuò)展法

對(duì)于這樣的一個(gè)問(wèn)題,一般暴力算法主要是枚舉所有子串的中心位置给梅,并在該位置上進(jìn)行擴(kuò)展,記錄并更新最長(zhǎng)回文子串的距離双揪。代碼實(shí)現(xiàn)如下:

#include "pch.h"
#include <iostream>
using namespace std;
int LongestPalindrome(const char* s, int n) {
    int i, j, c, length=0;
    if (s == 0 || n < 1)
        return 0;
    for (i = 0; i < n; i++) {
        for (j = 0; (j <= i) && (i + j <= n); j++) {
            if (s[i + j] != s[i - j])
                break;
            c = 2 * j + 1;
        }
        if (length <= c)
            length = c;
        for (j = 0; (j <= i) && (i + j <= n); ++j) {
            if (s[i - j] != s[i + j + 1])
                break;
            c = 2 * j + 2;
        }
        if (length <= c)
            length = c;
    }
    return length;
}
int main()
{
    char str[] = "acaacdbab";
    cout << LongestPalindrome(str, sizeof(str) / sizeof(char) - 1) << endl;
}

從代碼實(shí)現(xiàn)上可以看出暴力算法主要存在兩個(gè)缺陷:
1动羽、由于回文子串可以分為偶回文和奇回文兩種,例如abba是偶回文渔期,其對(duì)稱(chēng)中心是bb运吓,而aba則奇回文,其對(duì)稱(chēng)中心是b疯趟,二者判斷邏輯是不同的拘哨,所以需要分開(kāi)進(jìn)行處理;
2信峻、枚舉中心采用了逐個(gè)枚舉的方式倦青,并沒(méi)有有效利用到回文子串的對(duì)稱(chēng)性,使得代碼無(wú)法高效地運(yùn)行

解法二:Manacher算法

Manacher算法是對(duì)暴力算法的一個(gè)優(yōu)化盹舞。面對(duì)暴力算法的第一個(gè)缺點(diǎn)产镐,Manacher算法采用了一種方法將所有的偶回文和奇回文都轉(zhuǎn)化成為了奇回文,從而簡(jiǎn)化了代碼的判斷邏輯踢步。具體的做法是這樣的:在字符串的首尾癣亚,以及每?jī)蓚€(gè)字符中間插入一個(gè)源字符串沒(méi)有的特殊符號(hào),使得任意長(zhǎng)度的回文子串都可以轉(zhuǎn)化為奇回文获印。舉個(gè)例子逃糟,在源字符串中,有"cabbac"和"bab"兩個(gè)回文子串蓬豁,經(jīng)過(guò)處理后會(huì)轉(zhuǎn)變?yōu)?#c#a#b#b#a#c#"和"#b#a#b#"绰咽,全部都轉(zhuǎn)化為了奇回文。同時(shí)為了更好地處理數(shù)組的越界問(wèn)題地粪,在數(shù)組的首部添加一個(gè)哨兵位$取募。
而對(duì)于第二個(gè)缺點(diǎn),則是通過(guò)定義了一個(gè)回文半徑數(shù)組int P[]蟆技,其中P[i]表示以字符Si為中心的最長(zhǎng)回文子串向左或向右擴(kuò)張的長(zhǎng)度玩敏,例如

P數(shù)組求解實(shí)例.png
若假設(shè)原回文串的長(zhǎng)度為n,不管是偶回文還是奇回文质礼,擴(kuò)展后的回文串長(zhǎng)度都為2n+1旺聚,而P[i]保存回文半徑(包括S[i]在內(nèi)),則顯然滿(mǎn)足關(guān)系:(P[i]-1)*2+1=2n+1眶蕉,解得P[i]-1=n砰粹。因此,要求最長(zhǎng)回文子串長(zhǎng)度造挽,只需求P[i]-1的最大值即可碱璃。

在明白了如何利用P[i]求出原始回文子串的長(zhǎng)度后,算法接下來(lái)的重點(diǎn)便是如何求解P數(shù)組饭入?
Manacher算法增加了兩個(gè)變量id和mx嵌器,其中id代表最長(zhǎng)回文子串的中心字符位置,mx代表最長(zhǎng)回文子串的右邊界位置谐丢,即mx=P[id]+id爽航。因此,根據(jù)回文子串的對(duì)稱(chēng)性乾忱,我們可以得出一個(gè)重要的結(jié)論:若mx>i讥珍,則有P[i]≥min(P[2id-i],mx-i)*
證明如下:
當(dāng)mx>i時(shí),不妨記mx的對(duì)稱(chēng)點(diǎn)為my饭耳,再令j=2*id-i串述,則回文串可分為兩種情況:

  • 1、當(dāng)mx-i>P[j]寞肖,以S[j]為中心字符的回文子串被包含在以S[id]為中心字符的回文子串當(dāng)中纲酗,根據(jù)回文子串的對(duì)稱(chēng)性,必有有P[j]=P[i]新蟆,如下圖:
    情況1.png
  • 2觅赊、當(dāng)mx-i≤P[j]時(shí),以S[j]為中心的回文子串只有部分包含在最長(zhǎng)回文子串內(nèi)部(即圖中紅框位置)琼稻,因此可以推斷P[i]≥mx-i吮螺,至于超出mx后面的部分則需要另行匹配,如下圖:
    情況2.png

綜合以上兩種情況,便可以得出P[i]=min(P[2*id-i],mx-i)這一重要結(jié)論
當(dāng)mx≤i時(shí)鸠补,我們無(wú)法利用最長(zhǎng)回文子串的對(duì)稱(chēng)性對(duì)P[i]的值做出更多假設(shè)萝风,因此P[i]=1,然后繼續(xù)進(jìn)行中心擴(kuò)展匹配。
代碼實(shí)現(xiàn):

#include "pch.h"
#include <iostream>
#include <algorithm> 
using namespace std;
char str[] = "$#a#c#a#a#c#d#b#a#b#";
int Manacher(const char* str,int* P,int len) {
    int mx = 0;
    int id;
    int max_len = 0;
    for (int i = 1; i < len; i++){
        if (mx > i)
            P[i] = min(P[2 * id - i], mx - i);
        else
            P[i] = 1;
        while (str[i - P[i]] == str[i + P[i]])
            P[i]++;
        if (P[i] + i > mx) {
            mx = P[i] + i;
            id = i;
        }
        max_len = max(max_len, P[i] - 1);
    }
    return max_len;
}
int main()
{
    int length = sizeof(str) / sizeof(char) - 1;
    int* P = new int[length];
    memset(P, 0, length*sizeof(int));
    cout << "最長(zhǎng)回文子串長(zhǎng)度為:" << Manacher(str, P, length) << endl;
    delete[] P;
    return 0;
}
算法復(fù)雜度分析

顯然紫岩,對(duì)于Manacher算法规惰,其空間復(fù)雜度為O(n),而對(duì)于時(shí)間復(fù)雜度而言泉蝌,我們考慮以下兩種極端情況:
1歇万、字符串中不含任何回文子串琳省,例如abcdefg辨萍,在這種情況中椒楣,由于while中的條件始終不滿(mǎn)足牵舱,因此圆仔,循環(huán)執(zhí)行的次數(shù)應(yīng)當(dāng)為2(n-1)摇天,故此時(shí)時(shí)間復(fù)雜度為O(n)宠哄;

2员串、設(shè)1個(gè)僅由一個(gè)字符組成的字符串str呻粹,長(zhǎng)度為n壕曼,則經(jīng)過(guò)擴(kuò)展之后,長(zhǎng)度為2n+2等浊。由于這個(gè)字符串僅包含一個(gè)字符腮郊,故整個(gè)字符串的任意子串均為回文串,在這種情況下筹燕,while的執(zhí)行次數(shù)達(dá)到最多的情況轧飞。在計(jì)算P[i],由于對(duì)稱(chēng)性撒踪,僅需計(jì)算P[1....n+1]的值即可过咬,其計(jì)算規(guī)律如下表所示:
復(fù)雜度分析.png
從表中可以看出:

任取i∈{1,...,n+1},都滿(mǎn)足P[i]=i制妄,id=i,mx=2i>i
因此當(dāng)i>2時(shí)掸绞,有P[i]=min(P[2id-i],mx-i)=min(P[2(i-1)-i,2(i-1)-i)成立,
令j=2
id-i耕捞,則有P[j]=j衔掸,進(jìn)而有

  • P1[i]=min(P[2(i-1)-i,2(i-1)-i)=min(2(i-1)-i,2(i-1)-i)=i-2

注:這是經(jīng)過(guò)if判斷語(yǔ)句后得到的P[i]初始值,為了避免和最終P[i]值相混淆俺抽,此處記為P1[i]敞映,后續(xù)均以P1[i]代表初始值,P[i]代表最終值

隨后程序進(jìn)入了while循環(huán)磷斧,由于P1[i]=i-2振愿,P[i]=i捷犹,因此可以很容易看出while循環(huán)體最多僅執(zhí)行2次,故雖然代碼有2層循環(huán)冕末,但是在最壞的情況下萍歉,內(nèi)存while循環(huán)最多對(duì)n-1個(gè)字符進(jìn)行了2次循環(huán)(當(dāng)i=1時(shí),循環(huán)不執(zhí)行栓霜;當(dāng)i=2時(shí)翠桦,循環(huán)執(zhí)行1次),其余字符均不進(jìn)行循環(huán)胳蛮,因此整體代碼的時(shí)間復(fù)雜度是O(n)。由于最壞與最好的情況下丛晌,代碼的時(shí)間復(fù)雜度均為O(n)仅炊,因此代碼的平均時(shí)間復(fù)雜度為O(n)。

最后我測(cè)試了幾組極端數(shù)據(jù)澎蛛,所測(cè)試結(jié)果與最差情況下的證明結(jié)果相吻合抚垄,測(cè)試結(jié)果如下:
測(cè)試結(jié)果.png
注:其中count變量為計(jì)數(shù)器,用于計(jì)算對(duì)于每個(gè)字符谋逻,while的執(zhí)行次數(shù)呆馁。原始字符串長(zhǎng)度為n=7,擴(kuò)展后的字符串長(zhǎng)度為16(包括$符)毁兆,當(dāng)i=1時(shí)浙滤,內(nèi)層循環(huán)執(zhí)行0次;當(dāng)i=2時(shí)气堕,內(nèi)層循環(huán)執(zhí)行1次纺腊,當(dāng)i>8(n+1)時(shí),內(nèi)存循環(huán)體均不執(zhí)行茎芭,當(dāng)i=(3...n+1)揖膜,內(nèi)層循環(huán)執(zhí)行次數(shù)均為2次。

這也正是Manacher算法的精彩之處梅桩,巧妙地利用了之前所有計(jì)算出來(lái)的P[1...i-1]的值壹粟,在每次循環(huán)中對(duì)P[i]進(jìn)行快速賦值,避免P[i]的值每次都從1開(kāi)始計(jì)算宿百,大幅削減了比較次數(shù)趁仙,最終使得求解最長(zhǎng)回文子串長(zhǎng)度達(dá)到了線性級(jí)復(fù)雜度O(n),同樣思想也被運(yùn)用在KMP算法中犀呼。

總結(jié):

1幸撕、對(duì)于數(shù)組,字符串的一些問(wèn)題外臂,在需要分為奇數(shù)坐儿、偶數(shù)的情況下進(jìn)行討論時(shí),往往可以采用填充的方式,將其轉(zhuǎn)化為奇數(shù)的情況貌矿,從而簡(jiǎn)化判斷邏輯炭菌。同樣的做法也可以放入到一些樹(shù)的判斷當(dāng)中,通過(guò)填充特殊結(jié)點(diǎn)逛漫,使一棵普通二叉樹(shù)轉(zhuǎn)變?yōu)橥耆鏄?shù)黑低,甚至是滿(mǎn)二叉樹(shù),來(lái)使得問(wèn)題簡(jiǎn)化酌毡;
2克握、對(duì)于出現(xiàn)循環(huán)嵌套,且內(nèi)層循環(huán)的判斷條件與輸入數(shù)據(jù)相關(guān)枷踏,在判斷時(shí)間復(fù)雜度時(shí)菩暗,可以考慮判斷最差與最優(yōu)情況,若最優(yōu)與最差時(shí)間復(fù)雜度相同旭蠕,則一般情況下的時(shí)間復(fù)雜度應(yīng)當(dāng)與最優(yōu)或最差的時(shí)間復(fù)雜度相同停团。(有點(diǎn)類(lèi)似于數(shù)學(xué)上的夾逼定理,可以將求時(shí)間復(fù)雜度的過(guò)程近似地看成是求極限的過(guò)程)

參考資料:
1掏熬、《Manacher 算法》:https://subetter.com/articles/manacher-algorithm.html
2佑稠、《leetCode_5_Longest_Palindromic_Substring》:http://windliang.cc/2018/08/05/leetCode-5-Longest-Palindromic-Substring/
3、《編程之法——面試和算法心得》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旗芬,一起剝皮案震驚了整個(gè)濱河市舌胶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岗屏,老刑警劉巖辆琅,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異这刷,居然都是意外死亡婉烟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)暇屋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)似袁,“玉大人,你說(shuō)我怎么就攤上這事咐刨£夹疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵定鸟,是天一觀的道長(zhǎng)而涉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)联予,這世上最難降的妖魔是什么啼县? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任材原,我火速辦了婚禮,結(jié)果婚禮上季眷,老公的妹妹穿的比我還像新娘余蟹。我一直安慰自己,他們只是感情好子刮,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布威酒。 她就那樣靜靜地躺著,像睡著了一般挺峡。 火紅的嫁衣襯著肌膚如雪葵孤。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天沙郭,我揣著相機(jī)與錄音佛呻,去河邊找鬼。 笑死病线,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲤嫡。 我是一名探鬼主播送挑,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼暖眼!你這毒婦竟也來(lái)了惕耕?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诫肠,失蹤者是張志新(化名)和其女友劉穎司澎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體栋豫,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挤安,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丧鸯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛤铜。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丛肢,靈堂內(nèi)的尸體忽然破棺而出围肥,到底是詐尸還是另有隱情,我是刑警寧澤蜂怎,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布穆刻,位于F島的核電站,受9級(jí)特大地震影響杠步,放射性物質(zhì)發(fā)生泄漏氢伟。R本人自食惡果不足惜榜轿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腐芍。 院中可真熱鬧差导,春花似錦、人聲如沸猪勇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泣刹。三九已至助析,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椅您,已是汗流浹背外冀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掀泳,地道東北人雪隧。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像员舵,于是被迫代替她去往敵國(guó)和親脑沿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容