最長回文子串

問題定義

最長回文子串問題:給定一個字符串,求它的最長回文子串長度。

解法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閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容