LeetCode 5. Longest Palindromic Substring(最長回文子串)

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example:
Input: "cbbd"
Output: "bb"

大致意思就是給定一個字符串s,找到s中最長回文子串
回文的含義是:正著看和倒著看相同,如abba和abcba.
子串的含義是:在原串中連續(xù)出現(xiàn)的字符串片段。
這是一道經(jīng)典的算法題,在《算法競賽入門經(jīng)典》以及2016騰訊實習(xí)生校招筆試題中都曾遇到過,之前的文章中也曾解決過一個更復(fù)雜的問題
解決該問題最方便的方法是中心擴(kuò)展法,從中心向兩邊擴(kuò)展回文串,需要注意的是,回文串有兩種形式:長度為奇數(shù)的回文串(形如abcba)以及長度為偶數(shù)的回文串(形如abba).
使用這種方法尋找最長回文子串的時間復(fù)雜度為O(n?^2??),空間復(fù)雜度為O(1)。
代碼如下:

public class Solution {
    public String longestPalindrome(String s) {

        int n = s.length();
        int start = 0, maxLen = 0;
        for (int i = 0; i < n; i++) {   // 以i為中心向兩邊擴(kuò)展
            for (int j = 0; i - j >= 0 && i + j < n; j++) { // 長度為奇數(shù)的回文串(形如abcba)
                if (s.charAt(i - j) != s.charAt(i + j)) {
                    break;
                }
                if (2 * j + 1 > maxLen) {
                    maxLen = 2 * j + 1;
                    start = i - j;
                }
            }
            for (int j = 0; i - j >= 0 && i + 1 + j < n; j++) { // 長度為偶數(shù)的回文串(形如abba),中心點(兩個b)位置分別為i和i+1,向兩邊擴(kuò)展范圍(i-j,i+1+j)
                if (s.charAt(i - j) != s.charAt(i + 1 + j)) {
                    break;
                }
                if (2 * j + 2 > maxLen) {
                    maxLen = 2 * j + 2;
                    start = i - j;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
}

此外,解決該問題還可以使用Manacher算法(中文名馬拉車算法),它可以在O(N)的時間復(fù)雜度內(nèi)得到一個字符串中以每一個字符為中心的最長回文子串。而對于長度為偶數(shù)的回文串,比如abba,可以通過插入未出現(xiàn)過的特殊字符#,從而轉(zhuǎn)化成長度為奇數(shù)的回文串#a#b#b#a#
該算法的基本原理就是利用已知回文串的左半部分來推導(dǎo)右半部分
用數(shù)組p[i]表示以i為中心的最長回文子串半徑長度,從前向后掃描字符串s中的每一個字符計算出它們對應(yīng)的p[i],找到最大的p[i]即找到了s的最長回文子串。
在從前向后掃描的過程中,需要計算p[i]時一定已經(jīng)計算好了p[1]....p[i-1],假設(shè)現(xiàn)在掃描到了i+k這個位置,需要計算p[i+k]
定義max是以i+k之前的字符為中心的所有回文串所能延伸到的最遠(yuǎn)的位置(回文右邊界),假設(shè)這個字符的位置是i,即max = i + p[i];// i為i+k之前到達(dá)最遠(yuǎn)的回文串中心
這時i+k的位置分布有兩種情況:

  1. i+k這個位置不在前面的任何回文串內(nèi)部(不含max點),即 i+k >= max,這時初始化p[i+k] = 0;//本身是回文串
    然后p[i+k]左右延伸,即while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) p[i+k]++;

  2. i+k這個位置被前面以i為中心的回文串包含,即 i+k < max,這樣的話p[i+k]就不是從0開始了

對于第二種情況,根據(jù)回文串的性質(zhì),可知i+k這個位置的字符關(guān)于i與i-k對稱,這時p[i+k]又要分為以下三種情況來計算(黑色是以i為中心的回文串范圍,綠色是以i-k為中心的回文串范圍)

2.1

如上圖,若以i-k為中心的回文串有一部分在以i為中心的回文串范圍之外(綠色的左端在黑色范圍之外),則以i+k為中心的回文串范圍一定是橙色部分,有 p[i+k] = max - (i+k) = i + p[i] - i - k = p[i] - k,即 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i] - k < p[i-k])

  • 如果小于橙色部分,則違反了與i-k關(guān)于i的對稱性

  • 假設(shè)超出橙色部分,如圖中的紫色延長線c和d,根據(jù)以i為中心的回文子串性質(zhì),b部分與c部分相對于i對稱;同理根據(jù)以i-k為中心的回文子串性質(zhì),b部分與a部分相對于i-k對稱,最終得出a部分與d部分相對于i位置對稱,超出了以i為中心的回文子串(黑色)范圍,假設(shè)不成立。

2.2

如上圖,若以i-k為中心的回文串全部在以i為中心的回文串范圍內(nèi)未達(dá)到左側(cè)邊界(左側(cè)綠色在黑色范圍之內(nèi)),則以i+k為中心的回文串范圍一定是右側(cè)綠色部分,有p[i+k] = p[i-k],即 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i-k] < p[i] - k)

  • 如果小于右側(cè)綠色部分,同樣是違反了與i-k關(guān)于i的對稱性

  • 假設(shè)超出右側(cè)綠色部分,如圖中的紫色延長線c和d,根據(jù)以i為中心的回文子串性質(zhì),b部分與c部分相對于i對稱,a部分與d部分也相對于i對稱,最終得出a部分與b部分相對于i-k位置對稱,超出了以i-k為中心的回文子串(左側(cè)綠色)范圍,假設(shè)不成立。

2.3

如上圖,若以i-k為中心的回文串全部在以i為中心的回文串范圍內(nèi)恰好達(dá)到左側(cè)邊界(左側(cè)綠色到達(dá)黑色范圍左邊界),則有 i-k - p[i-k] = i - p[i],即 p[i-k] = p[i] - k
以i+k為中心的回文串范圍一定包含右側(cè)綠色部分,并有可能超出(如圖橙色部分),這時初始化 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i-k] = p[i] - k),并向外延伸while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) p[i+k]++;

綜合上面所有情況,總結(jié)出核心代碼:

p[i+k] = 0;
if(i+k < max) {
    p[i+k] = Math.min(p[i-k], p[i]-k);
}  
while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) {
    p[i+k]++;
}

上面分析中把當(dāng)前的字符定義在位置i+k,配合i及i-k這種對稱的命名方式是為了便于我們理解,在實際編程中當(dāng)前字符的位置通常為i,最長延伸回文串的中心點通常定義成id(id < i),則i關(guān)于id的對稱位置為id - (i - id) = 2 * id - i,把上面的思想帶入到實際編程中:

在遍歷到位置為i的字符時,已知以位置為id的字符為中心的回文串已延伸到的最遠(yuǎn)位置max = id + p[id],如果當(dāng)前字符在這個回文串中,我們就要賦值 p[i] = Math.min(p[2 * id - i], max - i),即以當(dāng)前位置i關(guān)于id的對稱位置2 * id - i為中心的回文串半徑與該對稱位置距離最長延展回文串左邊界長度max - i二者間的較小值。代碼如下:

public class Solution {
    public String longestPalindrome(String s) {

        String t = "#";
        for (int i = 0; i < s.length(); i++) { // 插入#,統(tǒng)一轉(zhuǎn)化為長度為奇數(shù)的回文串
            t += s.charAt(i) + "#";
        }
        int[] p = new int[t.length()];       // p[i]表示以i為中心的最長回文子串半徑長度
        int maxP = 0, maxC = 0;              // 數(shù)組p中的最大值,最長回文子串的中心點
        int id = 0;                          // 當(dāng)前位置之前已到達(dá)最遠(yuǎn)的回文串中心
        int max = id + p[id];                // 當(dāng)前查找位置之前,已知能影響最右邊的串
        for (int i = 0; i < t.length(); i++) {
            if (i < max) {                   // 當(dāng)前字符在之前最遠(yuǎn)回文串的范圍之內(nèi)
                p[i] = Math.min(p[2 * id - i], max - i);
            }
            while (i - p[i] >= 0 && i + p[i] < t.length() && t.charAt(i - p[i]) == t.charAt(i + p[i])) {
                p[i]++;
            }
            if (i + p[i] > max) {   // 更新最遠(yuǎn)回文串
                id = i;
                max = id + p[id];
            }
            if (p[i] > maxP) {      // 保留最大值信息
                maxP = p[i];
                maxC = i;
            }
        }
        if (t.charAt(maxC) == '#') { // 還原長度為偶數(shù)的回文串
            return s.substring((maxC - 2) / 2 - (maxP - 2) / 2, (maxC - 2) / 2 + 1 + (maxP - 2) / 2 + 1);
        } else {                     // 還原長度為奇數(shù)的回文串
            return s.substring((maxC - 1) / 2 - (maxP - 1) / 2, (maxC - 1) / 2 + (maxP - 1) / 2 + 1);
        }
    }
}

總體來說,Manacher算法還是比較復(fù)雜的,不會有人要求你在45分鐘的coding session中去實現(xiàn)它,大致了解即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,699評論 2 370

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

  • 最長回文子串——Manacher 算法 1. 問題定義 最長回文字符串問題:給定一個字符串,求它的最長回文子串長度...
    林大鵬閱讀 2,790評論 0 6
  • 問題定義 最長回文子串問題:給定一個字符串,求它的最長回文子串長度。 解法1:暴力解法 找到字符串的所有子串,判斷...
    HITMiner閱讀 692評論 0 2
  • 最長回文串問題是一個經(jīng)典的算法題。 0. 問題定義 最長回文子串問題:給定一個字符串,求它的最長回文子串長度。如果...
    曾會玩閱讀 4,048評論 2 25
  • 上一篇KMP算法之后好幾天都沒有更新,今天介紹最長回文子串。 首先介紹一下什么叫回文串,就是正著讀和倒著讀的字符順...
    zero_sr閱讀 2,316評論 2 8
  • 這次要記錄的是一個經(jīng)典的字符串的題目,也是一個經(jīng)典的馬拉車算法的實踐。相信在很多地方都會考到或者問到這道題目,這道...
    檸檬烏冬面閱讀 2,920評論 0 9