5. 最長(zhǎng)回文子串(Python)

題目

給定一個(gè)字符串 s,找到 s 中最長(zhǎng)的回文子串剩蟀。你可以假設(shè) s 的最大長(zhǎng)度為 1000。

示例

示例 1:

輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個(gè)有效答案。
示例 2:

輸入: "cbbd"
輸出: "bb"

解答

方案1:暴力求解

遍歷每一個(gè)子串,構(gòu)建回文串判定函數(shù)(is_palindromic_string)钧萍,用于判定每個(gè)子串是否為回文串,隨時(shí)更新當(dāng)前最大回文子串(max_substr)及其長(zhǎng)度(max_len)政鼠。

class Solution:
    def longestPalindrome(self, s):
        is_palindromic_string = lambda s: s == s[::-1]  # 回文串判定函數(shù)
        max_len, max_substr = 0, ''

        for i in range(len(s)):
            for j in range(i+1, len(s)+1):
                cur_substr = s[i:j]                     # 取當(dāng)前子串
                print(cur_substr)
                if is_palindromic_string(cur_substr):
                    cur_len = len(cur_substr)           # 當(dāng)前子串長(zhǎng)度
                    if cur_len > max_len:
                        max_len = cur_len
                        max_substr = cur_substr         # 當(dāng)前最長(zhǎng)子串
        return max_substr

算法的時(shí)間復(fù)雜度為O(N^3)风瘦,果然,時(shí)間超過(guò)限制缔俄。

方案2:動(dòng)態(tài)規(guī)劃

這里弛秋,我們采用以空間換時(shí)間的動(dòng)態(tài)規(guī)劃策略器躏,網(wǎng)上很多案例晦澀難懂俐载,我們這里稍作改造。設(shè)我們的輸入字符串為s:
1.構(gòu)造二維矩陣dp登失,維度為len(s)*len(s)遏佣,dp[left][right]表示子串s[left:right+1]是否為回文串,left和right均為下標(biāo)索引揽浙,范圍在0~len(s)之間状婶,且left<right意敛,因此dp矩陣中的每一個(gè)變量均為布爾量;
2.構(gòu)造狀態(tài)轉(zhuǎn)移方程膛虫,考慮三種情況草姻,
(1)left = right,當(dāng)前子串只有一個(gè)字符稍刀,一定為回文串撩独;
(2)right = left + 1,當(dāng)前子串只有兩個(gè)字符账月,判斷這兩個(gè)字符是否相等综膀;
(3)right > left + 1,當(dāng)前子串有多個(gè)字符局齿,判斷根據(jù)首位字符是否相等以及除去首位字符剩余子串是否為回文串判定當(dāng)前子串剧劝。
狀態(tài)轉(zhuǎn)移方程為:
dp\left [ left \right ]\left [right \right ]= \left\{\begin{matrix}True, \qquad \qquad \qquad\qquad\qquad \qquad \qquad \qquad \qquad \qquad \qquad left=right; \\ str\left [ left \right ]==str\left [ right \right ], \qquad \quad \qquad \qquad \qquad \qquad \qquad right-left=1; \\ str\left [ left \right ]==str\left [ right \right ] \& dp \left[ left+1 \right ]\left [right-1 \right ],\qquad right-left>1; \end{matrix}\right.
或者把前兩種情況歸類:
dp\left [ left \right ]\left [right \right ]= \left\{\begin{matrix}str\left [ left \right ]==str\left [ right \right ], \quad \quad \qquad \qquad \qquad \qquad \qquad right-left<=1; \\ str\left [ left \right ]==str\left [ right \right ] \& dp \left[ left+1 \right ]\left [right-1 \right ],\qquad right-left>1; \end{matrix}\right.

class Solution:
    def longestPalindrome(self, s):

        dp = [[False]*len(s) for _ in range(len(s))]
        max_start, max_len = 0, 0                       # 最長(zhǎng)回文子串開(kāi)始位置及長(zhǎng)度
        for right in range(len(s)):                     # 右指針先走
            for left in range(right+1):                 # 左指針跟著右指針
                if right - left < 2:                    # 前兩種情況
                    dp[left][right] = (s[left] == s[right])
                else:                                   # 最后一種情況
                    dp[left][right] = (s[left] == s[right]) and dp[left+1][right-1]
                # cur_substr = s[left:right+1]          # 當(dāng)前考察的子串
                cur_len = right + 1 - left              # 當(dāng)前子串長(zhǎng)度為 right + 1 - left
                if dp[left][right] and max_len < cur_len:

                    max_start = left
                    max_len = cur_len

        return s[max_start:max_start + max_len]

時(shí)間和空間復(fù)雜度O(N^2),通過(guò)了驗(yàn)證抓歼,但是這個(gè)程序還是比較慢的:
執(zhí)行用時(shí) : 8980 ms, 擊敗了7.80% 的用戶
內(nèi)存消耗 : 20.7 MB, 中擊敗了17.62% 的用戶

方案3:中心擴(kuò)展

回文串有兩個(gè)特點(diǎn):
1.當(dāng)回文串的左側(cè)和右側(cè)同時(shí)增加相同的字符時(shí)讥此,構(gòu)成的字符串也是回文串;
2.只有一個(gè)字符的字符串本身也是回文串谣妻;

根據(jù)這個(gè)特點(diǎn)暂论,我們從頭到尾遍歷字符串,并且每遍歷到一個(gè)位置時(shí)拌禾,我們都對(duì)這個(gè)位置的字符進(jìn)行左右擴(kuò)展取胎,如果左右兩頭的元素相同,又可以構(gòu)成新的回文子串湃窍,循環(huán)執(zhí)行下去闻蛀,直到左右兩端元素不相同了為止,在此過(guò)程中您市,記錄下每次擴(kuò)展得到的最長(zhǎng)子串起始位置和長(zhǎng)度觉痛。需要注意的是,根據(jù)回文子串元素個(gè)數(shù)茵休,我們需要考慮兩種情況薪棒,一種是奇數(shù)個(gè),一種是偶數(shù)個(gè)榕莺,這里我們用左右擴(kuò)展的起始下標(biāo)進(jìn)行區(qū)分俐芯。

class Solution:
    def longestPalindrome(self, s):
        def extend(start_idx, end_idx, max_start, max_len):
            """
            擴(kuò)展函數(shù),用于得到向左向右同步擴(kuò)展后的最長(zhǎng)回文子串
            :param start_idx: 向左擴(kuò)展的起始位置
            :param end_idx: 向右擴(kuò)展的起始位置
            :param max_start: 當(dāng)前最長(zhǎng)回文子串的左指針
            :param max_len: 當(dāng)前最大長(zhǎng)度
            :return: 當(dāng)前最長(zhǎng)回文子串的起始位置和長(zhǎng)度
            """

            while 0 <= start_idx <= end_idx < len(s):   # 子串起止下標(biāo)合法時(shí)
                if s[start_idx] == s[end_idx]:          # 如果新增的兩端字符相等
                    cur_str = s[start_idx:end_idx]      # 當(dāng)前子串是回文串
                    cur_len = end_idx + 1 - start_idx   # 當(dāng)前子串長(zhǎng)度
                    if max_len < cur_len:
                        max_len = cur_len
                        max_start = start_idx
                    start_idx -= 1                      # 左指針向左移動(dòng)一位
                    end_idx += 1                        # 右指針向右移動(dòng)一位
                else:
                    break
            return max_start, max_len

        max_start, max_len = 0, 0                       # 初始化最長(zhǎng)回文子串開(kāi)始位置及長(zhǎng)度

        for i in range(len(s)):                         # 進(jìn)行一次遍歷
            left = right = i                            # 判斷長(zhǎng)度為奇數(shù)的回文子串開(kāi)始和結(jié)束位置
            # 從i位置向兩邊擴(kuò)展钉鸯,搜尋可以得到的最大回文子串
            max_start, max_len = extend(left, right, max_start, max_len)

            left, right = i, i+1                        # 判斷長(zhǎng)度為偶數(shù)的回文子串開(kāi)始和結(jié)束位置
            # 從i位置向左擴(kuò)展吧史,從i+1位置向右擴(kuò)展,搜尋可以得到的最大回文子串
            max_start, max_len = extend(left, right, max_start, max_len)

        return s[max_start:max_start + max_len]

網(wǎng)上廣為流傳的還有一個(gè)辦法唠雕,遍歷字符串中每一個(gè)字符贸营,主要考慮兩種情況:

  1. 向左和向右各擴(kuò)展一個(gè)字符吨述,查看構(gòu)成的新子串是否為回文串;
  2. 只向右擴(kuò)展一個(gè)字符钞脂,查看構(gòu)成的新子串是否為會(huì)問(wèn)串揣云。
    這種方法要更加快些。
class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        is_palindromic_string = lambda s: s == s[::-1]

        str_length = len(s)
        cur_length = 0
        start_index = 0
        for i in range(str_length):

            cur_start_index = i - cur_length - 1                                # 向左擴(kuò)展一個(gè)字符
            cur_substr = s[cur_start_index: cur_start_index+cur_length+2]       # 向右擴(kuò)展一個(gè)字符冰啃,獲得子串

            if cur_start_index >= 0 and is_palindromic_string(cur_substr):      # 判斷擴(kuò)展后的子串是否回文
                start_index = cur_start_index                                   # 更新子串起始下標(biāo)
                cur_length += 2                                                 # 更新當(dāng)前最長(zhǎng)子串長(zhǎng)度

            else:
                cur_start_index = i - cur_length                                # 向左不擴(kuò)展
                cur_substr = s[cur_start_index: cur_start_index+cur_length+1]   # 向右擴(kuò)展一個(gè)字符灵再,獲得子串
                if cur_start_index >= 0 and is_palindromic_string(cur_substr):  # 判斷擴(kuò)展后的子串是否回文
                    start_index = cur_start_index                               # 更新子串起始下標(biāo)
                    cur_length += 1                                             # 更新當(dāng)前最長(zhǎng)子串長(zhǎng)度

        return s[start_index: start_index + cur_length]

其實(shí)還有疑問(wèn),為什么當(dāng)兩邊擴(kuò)展后就無(wú)需單邊擴(kuò)展亿笤,以及為何起始下標(biāo)要這樣設(shè)置翎迁,懂的大佬請(qǐng)?jiān)谠u(píng)論中留言。净薛。
執(zhí)行用時(shí) : 92 ms, 擊敗了96.52% 的用戶
內(nèi)存消耗 : 13 MB, 擊敗了94.22% 的用戶

方案4:Manacher's Algorithm(馬拉車算法)

我們可以看到上面需要考慮子串是長(zhǎng)度為奇數(shù)還是偶數(shù)兩種情況汪榔,如何可以統(tǒng)一起來(lái)?馬拉車算法應(yīng)運(yùn)而生肃拜。我們?cè)谳斎胱址谩?”隔開(kāi)痴腌,且首位也添加“#”號(hào),如“cbbcd”變成“#c#b#b#c#d#”燃领,這樣就可以確保字符串的長(zhǎng)度為奇數(shù)了士聪。

class Solution:
    def longestPalindrome(self, s):
        def get_length(string, index):
            # 循環(huán)求出index為中心的最長(zhǎng)回文字串
            start, length = index // 2, 0
            r_ = len(string)
            for i in range(1, index + 1):
                if index + i < r_ and string[index - i] == string[index + i]:
                    start = (index - i) // 2
                    length += 1
                else:
                    break
            return start, length

        s_list = [c for c in s]
        new_s = '#' + '#'.join(s_list) + '#'        # 形成新串

        max_start = max_length = 0
        length = len(new_s)
        for index in range(0, length):
            start, r_length = get_length(new_s, index)
            if max_length < r_length:
                max_start, max_length = start, r_length
        return s[max_start: max_start+max_length]

如有疑問(wèn),歡迎評(píng)論區(qū)留言~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猛蔽,一起剝皮案震驚了整個(gè)濱河市剥悟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌曼库,老刑警劉巖区岗,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異毁枯,居然都是意外死亡慈缔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門种玛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)藐鹤,“玉大人,你說(shuō)我怎么就攤上這事赂韵∮榻冢” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵右锨,是天一觀的道長(zhǎng)括堤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)绍移,這世上最難降的妖魔是什么悄窃? 我笑而不...
    開(kāi)封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蹂窖,結(jié)果婚禮上轧抗,老公的妹妹穿的比我還像新娘。我一直安慰自己瞬测,他們只是感情好横媚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著月趟,像睡著了一般灯蝴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孝宗,一...
    開(kāi)封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天穷躁,我揣著相機(jī)與錄音,去河邊找鬼因妇。 笑死问潭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的婚被。 我是一名探鬼主播狡忙,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼址芯!你這毒婦竟也來(lái)了灾茁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谷炸,失蹤者是張志新(化名)和其女友劉穎删顶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體淑廊,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逗余,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了季惩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片录粱。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖画拾,靈堂內(nèi)的尸體忽然破棺而出啥繁,到底是詐尸還是另有隱情,我是刑警寧澤青抛,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布旗闽,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏适室。R本人自食惡果不足惜嫡意,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捣辆。 院中可真熱鬧蔬螟,春花似錦、人聲如沸汽畴。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忍些。三九已至鲁猩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罢坝,已是汗流浹背廓握。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炸客,地道東北人疾棵。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像痹仙,于是被迫代替她去往敵國(guó)和親是尔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354