Manacher算法

??首先讓我們來(lái)看Leetcode上的一道題绎巨。

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

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

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

??題意解析:給定一個(gè)字符串S做祝,求這個(gè)字符串的最長(zhǎng)回文子串。所謂回文字符串就是正著讀和反著讀結(jié)果都一樣的字符串株搔,如aba剖淀,bab。
??解法:
(1)暴力求解法:即循環(huán)去枚舉字符串中的每個(gè)字符纤房,這種解法的時(shí)間復(fù)雜度為O(n^3)纵隔,再leetcode上應(yīng)該過(guò)不了,所以沒(méi)有去實(shí)現(xiàn)炮姨。
(2)中心擴(kuò)展法:即以某個(gè)元素為中心捌刮,分別去計(jì)算假設(shè)回文子串長(zhǎng)度為偶數(shù)的情況和回文子串長(zhǎng)度為奇數(shù)的情況下的最長(zhǎng)的回文串,這種解法時(shí)間復(fù)雜度為O(n^2)舒岸,時(shí)間復(fù)雜度比第一種算法時(shí)間減少了一個(gè)指數(shù)級(jí)绅作,嘗試著去實(shí)現(xiàn)它,代碼如下:

package cn.test.leetcode;

/**
 * Created by GavinCee on 2019/5/1.
 */
public class PalindromicStr {

    public static void main(String[] args) {
        PalindromicStr palindromicStr = new PalindromicStr();
        String testStr1 = "babad";
        String testStr2 = "cbbd";
        String testStr3 = "gabbacg";
        String testStr4 = "gabbac";
        String testStr5 = "cabbac";
        System.out.println(palindromicStr.longestPalindrome(testStr1));
        System.out.println(palindromicStr.longestPalindrome(testStr2));
        System.out.println(palindromicStr.longestPalindrome(testStr3));
        System.out.println(palindromicStr.longestPalindrome(testStr4));
        System.out.println(palindromicStr.longestPalindrome(testStr5));
    }

    public String longestPalindrome(String s) {
        if(s == null) {
            return null;
        }
        if(s.equals("")) {
            return "";
        }
        int maxLength = 0;
        String result = "";
        int len = 1;
        if(s.length() > 1) {
            len = s.length() - 1;
        }
        for (int i = 0; i < len; i++) {
            String tmp = s.charAt(i) + "";
            //1. if the substring length is odd ,so the index is mid.
            for(int j = i - 1, k = i + 1;j >=0 && k < s.length(); j--,k++) {
                if(s.charAt(j) == s.charAt(k)) {
                    tmp = s.charAt(j) + tmp + s.charAt(k);
                } else {
                    break;
                }
            }
            if(tmp.length() > maxLength) {
                maxLength = tmp.length();
                result = new String(tmp);
            }
            //2' if the substring length is even, the right need plus 2
            if (i + 1 < s.length() &&  s.charAt(i) == s.charAt(i + 1)) {
                tmp = s.charAt(i) + "" + s.charAt(i + 1);
                for(int j = i - 1, k = i + 2;j >=0 && k < s.length(); j--,k++) {
                    if(s.charAt(j) == s.charAt(k)) {
                        tmp = s.charAt(j) + tmp + s.charAt(k);
                    } else {
                        break;
                    }
                }
                if(tmp.length() > maxLength) {
                    maxLength = tmp.length();
                    result = new String(tmp);
                }
            }
        }
        return result;
    }

}

??提交到leetcode后蛾派,仍然還是會(huì)報(bào)時(shí)間超時(shí)俄认。所以這種解法還被pass掉了。暫時(shí)無(wú)優(yōu)化思路洪乍,借助百度眯杏,了解到還有一種時(shí)間復(fù)雜度為O(n)的算法,下面強(qiáng)調(diào)介紹一下該算法壳澳,并順便整理一下思路岂贩。
(3)Manacher算法
Manacher算法提供了一種巧妙的方法,講長(zhǎng)度為奇數(shù)的回文串和長(zhǎng)度為偶數(shù)的回文串一起考慮進(jìn)來(lái)巷波,具體做法是在原字符串的每?jī)蓚€(gè)相鄰的字符中間插入一個(gè)分隔符萎津,同時(shí)在原字符串的首尾也都添加上分隔符,該分隔符的要求是不要在原字符串內(nèi)出現(xiàn)抹镊,避免出現(xiàn)混淆锉屈。如下所示:

原字符串 轉(zhuǎn)換后的字符串
babad #b#a#b#a#d#

在Manacher算法中用到一個(gè)非常重要的輔助數(shù)組,我們將轉(zhuǎn)換后的字符串定義為字符數(shù)組str[i]垮耳,其輔助數(shù)組定義為len[i]颈渊。其中l(wèi)en[i]中的元素為以str[i]為中心時(shí)的回文串的最右端字符到str[i]的位置的長(zhǎng)度,如下圖所示氨菇,當(dāng)i等于3時(shí)儡炼,str[i]為a妓湘,以str[i]為中心的回文串是#b#a#b#查蓉,則len[i]為最右字符#到a的長(zhǎng)度,即len[i]=4榜贴。

i 0 1 2 3 4 5 6 7 8 9 10
str[i] # b # a # b # a # d #
len[i] 1 2 1 4 1 4 1 2 1 2 1

該輔助數(shù)組len[i]有一個(gè)性質(zhì)豌研,就是len[i] -1就等于該位置所在的字符為中心的原字符串的回文子串的長(zhǎng)度妹田。
證明:在轉(zhuǎn)換后的字符串str,因?yàn)榍昂蠖疾迦肓朔指舴楣玻械幕匚淖哟拈L(zhǎng)度都是奇數(shù)鬼佣,len[i]為回文最右字符到回文中心的長(zhǎng)度,那么對(duì)于以str[i]為中心的回文子串的長(zhǎng)度就是2*len[i] - 1霜浴,算上首尾的分隔符晶衷,那么該回文子串會(huì)有l(wèi)en[i]個(gè)分隔符,所以說(shuō)原回文子串的長(zhǎng)度就是(2len[i] - 1)- len[i] = len[i] - 1阴孟。
那么計(jì)算最長(zhǎng)的回文子串長(zhǎng)度晌纫,即為計(jì)算len[i]數(shù)組中的最大值。
假設(shè)0<= j <= i永丝,從左往右開始計(jì)算len[i]锹漱,在計(jì)算len[i]的時(shí)候,由于j<=i慕嚷,那么在計(jì)算len[i]之前l(fā)en[j]已經(jīng)計(jì)算過(guò)了哥牍,假設(shè)mx為之前計(jì)算過(guò)的回文子串的最右端點(diǎn)位置,id為這個(gè)回文子串的中心點(diǎn)位置喝检,那么len[id] = mx- id + 1嗅辣。
(1)當(dāng)i <= mx
此時(shí)i在以id為中心的回文串,假設(shè)i相對(duì)于中心點(diǎn)id的對(duì)稱點(diǎn)為j蛇耀,len[j]已經(jīng)計(jì)算過(guò)了辩诞。

假設(shè)len[j] < mx - i,即如下圖所示


1.png

此時(shí)說(shuō)明以j為中心的回文串一定在以id為中心的回文串內(nèi)部纺涤,且i和j關(guān)于id對(duì)稱译暂,由回文串的定義可知,一個(gè)回文串反過(guò)來(lái)仍然是回文串撩炊,所以以i為中心的回文串長(zhǎng)度至少和以j為中心的回文串長(zhǎng)度相等外永,即len[i] >= len[j],因?yàn)閘en[j] < mx - i拧咳,所以i + len[j] < mx伯顶,

假設(shè)len[j] >= mx - i,如下圖所示


2.png

由于回文的對(duì)稱性骆膝,說(shuō)明以i為中心的回文串可能延伸到mx之外祭衩,而大于mx的部分的字符還沒(méi)有進(jìn)行匹配比較,所以要從mx+1位置開始一個(gè)一個(gè)字符匹配知道失配阅签,匹配完成后及時(shí)更新mx和對(duì)應(yīng)的id以及l(fā)en[i]掐暮。
(2)當(dāng)i >= mx
由于i比mx還大,說(shuō)明對(duì)于中心點(diǎn)為i的回文串還沒(méi)有計(jì)算過(guò)政钟,這個(gè)時(shí)候只能左右展開一個(gè)個(gè)字符的匹配路克,匹配完成之后更新mx位置和對(duì)應(yīng)的id以及l(fā)en[i].
按照以上思路實(shí)現(xiàn)代碼邏輯如下:

package cn.test.leetcode;

/**
 * Created by GavinCee on 2019/5/1.
 */
public class PalindromicStr2 {

    public static void main(String[] args) {
        PalindromicStr2 palindromicStr = new PalindromicStr2();
        String testStr1 = "babad";
        String testStr2 = "cbbd";
        String testStr3 = "gabbacg";
        String testStr4 = "gabbac";
        String testStr5 = "cabbac";
        String testStr = "babadada";
        System.out.println(palindromicStr.longestPalindrome(testStr));
        System.out.println(palindromicStr.longestPalindrome(testStr1));
        System.out.println(palindromicStr.longestPalindrome(testStr2));
        System.out.println(palindromicStr.longestPalindrome(testStr3));
        System.out.println(palindromicStr.longestPalindrome(testStr4));
        System.out.println(palindromicStr.longestPalindrome(testStr5));
    }

    public String longestPalindrome(String s) {
        if(s == null || s.length() < 1){
            return "";
        }
        StringBuilder manacherStr = new StringBuilder("#");
        for (int i = 0; i < s.length(); i++) {
            manacherStr.append(s.charAt(i) + "#");
        }

        int[] len = new int[manacherStr.length()];
        len[0] = 1;
        int maxRightIndex = 0;
        int index = 0;
        int maxLength = len[0];
        int maxIndex = 0;
        for(int i = 1; i < manacherStr.length(); i++) {
            if(maxRightIndex > i) {
                int j = 2 * index - i;
                if(len[j] < maxRightIndex - i) {
                    len[i] = len[j];
                } else {
                    len[i] = maxRightIndex - i + 1;
                    //從maxRightIndex開始匹配樟结,已經(jīng)匹配了len[i]的長(zhǎng)度了,所以左側(cè)是i-len[i]
                    while (i - len[i] >= 0 && i+len[i] < manacherStr.length() && manacherStr.charAt(i - len[i]) == manacherStr.charAt(i+len[i])) {
                        len[i]++;
                    }
                }
            }else {
                //此時(shí)還沒(méi)有匹配
                len[i] = 1;
                while (i - len[i] >= 0 && i+len[i] < manacherStr.length() && manacherStr.charAt(i - len[i]) == manacherStr.charAt(i+len[i])) {
                    len[i] = len[i] + 1;
                }
            }
            if(len[i] + i - 1> maxRightIndex) {
                maxRightIndex = len[i] + i - 1;
                index = i;
            }
            if(len[i] > maxLength) {
                maxLength = len[i];
                maxIndex = i;
            }
        }
        //所以最大回文長(zhǎng)度就是len[] - 1;
        String resTmp = manacherStr.substring(maxIndex - maxLength + 1, maxIndex + maxLength -1);
        return resTmp.replaceAll("#", "");
    }

}

??提交運(yùn)行通過(guò)精算。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓢宦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子灰羽,更是在濱河造成了極大的恐慌驮履,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廉嚼,死亡現(xiàn)場(chǎng)離奇詭異疲吸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)前鹅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門摘悴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舰绘,你說(shuō)我怎么就攤上這事蹂喻。” “怎么了捂寿?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵口四,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我秦陋,道長(zhǎng)蔓彩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任驳概,我火速辦了婚禮赤嚼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顺又。我一直安慰自己更卒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布稚照。 她就那樣靜靜地躺著蹂空,像睡著了一般。 火紅的嫁衣襯著肌膚如雪果录。 梳的紋絲不亂的頭發(fā)上上枕,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音弱恒,去河邊找鬼辨萍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛斤彼,可吹牛的內(nèi)容都是我干的分瘦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼琉苇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘲玫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起并扇,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤去团,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后穷蛹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體土陪,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年肴熏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鬼雀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛙吏,死狀恐怖源哩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸦做,我是刑警寧澤励烦,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站泼诱,受9級(jí)特大地震影響坛掠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜治筒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一屉栓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耸袜,春花似錦系瓢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胰锌,卻和暖如春骗绕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背资昧。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工酬土, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人格带。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓撤缴,卻偏偏與公主長(zhǎng)得像刹枉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屈呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355