KMP算法介紹(力扣28.實(shí)現(xiàn)strStr())

學(xué)習(xí)筆記三 KMP算法介紹

本題來(lái)自:力扣28.實(shí)現(xiàn)strStr()

題目描述

簡(jiǎn)單來(lái)說(shuō),題目要求是從一個(gè)字符串 haystack 中找到另一個(gè)字符串 needle 出現(xiàn)的第一個(gè)位置。如果按暴力解法楷拳,題目解答很容易想到是 O(m * n) 的時(shí)間復(fù)雜度讨衣。那么,有沒(méi)有一種解法没炒,它的時(shí)間復(fù)雜度可以降到 O(m + n) 呢涛癌?

有!這就是我們接下來(lái)要介紹的 KMP 算法

KMP的主要思想

  • KMP的主要思想是:當(dāng)出現(xiàn)字符串不匹配時(shí)送火,可以知道一部分之前已經(jīng)匹配的文本內(nèi)容拳话,避免從頭再去做匹配了。這也是我們能將時(shí)間復(fù)雜度降低的本質(zhì)种吸。

KMP實(shí)現(xiàn)的關(guān)鍵步驟

一弃衍、構(gòu)建 next 數(shù)組

先來(lái)解釋幾個(gè)名詞:前綴,后綴骨稿,最長(zhǎng)公共前后綴笨鸡,前綴表

前綴:不包含最后一個(gè)字符的,并以第一個(gè)字符為開(kāi)頭的所有子字符串
后綴:不包含第一個(gè)字符的坦冠,并以最后一個(gè)字符為結(jié)尾的所有子字符串

最長(zhǎng)公共前后綴:對(duì)于一個(gè)字符而言形耗,最長(zhǎng)的相同的前綴后綴
前綴表:前綴表是記錄下標(biāo) i 之前(包括 i)的字符串中,有多大長(zhǎng)度的最長(zhǎng)公共前后綴

而我們要構(gòu)建的 next 數(shù)組辙浑,就是所謂的前綴表激涤。

KMP 算法實(shí)現(xiàn)的第一個(gè)難點(diǎn)便是 next 數(shù)組的構(gòu)建求解,即 對(duì)于一個(gè)子串,如何去求它的最長(zhǎng)公共前后綴倦踢?

構(gòu)造 next 數(shù)組其實(shí)就是計(jì)算短串的前綴表的過(guò)程送滞,其關(guān)鍵步驟主要有如下三步:

  • 初始化
  • 處理前后綴不相同的情況
  • 處理前后綴相同的情況

1,初始化

首先我們定義一個(gè)函數(shù) getNext 去構(gòu)建 next 數(shù)組辱挥,函數(shù)參數(shù)為指向 next 數(shù)組的指針犁嗅,和一個(gè)字符串。

void getNext(int* next, string needle)   //利用函數(shù)封裝晤碘,使主函數(shù)邏輯更加簡(jiǎn)潔

由于我們要比較一個(gè)字符串的前綴后綴褂微,那就必須有兩個(gè)指針?lè)謩e在字符串的開(kāi)頭和結(jié)尾。而對(duì)于 next 而言园爷,第一位只有一個(gè)字母的情況前綴后綴都是 0 宠蚂,可以直接初始化。

int left = 0;
next[0] = left;
for (int right = 1; right < needle.size(); right++) {
        
}

2童社,后綴不相同

如果在中間某個(gè)位置發(fā)現(xiàn) right 與 left 的值不相同求厕,就需要不斷進(jìn)行回溯。

由于 next 數(shù)組保留著前后綴相同的位置扰楼,那么回溯的位置便是 next 數(shù)組中 left 的前一個(gè)位置 left - 1 呀癣。如果回溯后的 left 仍然不等于 right 位置的字符,那就繼續(xù)回溯灭抑,直到 left 到達(dá)字符串左邊界十艾。但是為了防止 left - 1 溢出數(shù)組邊界,left = 1 時(shí)便就是邊界了腾节,因此判斷條件為 left > 0忘嫉。

while (left > 0 && needle[left] != needle[right]) {
    left = next[left - 1];
}

3,后綴相同

如果 left 和 right 的后綴相同案腺,那么說(shuō)明找到了相同的前后綴庆冕。left,right 遞增劈榨,同時(shí)把 left 的值賦給 next 數(shù)組访递。

if (needle[left] == needle[right]) {
    left++;
}
next[right] = left;

4,完整代碼

因此同辣,構(gòu)建 next 數(shù)組完整代碼如下:(時(shí)間復(fù)雜度O(n))

    void getNext(int* next, string needle) {
        int left = 0;
        next[0] = left;
        for (int right = 1; right < needle.size(); right++) {
            while (left > 0 && needle[left] != needle[right]) {
                left = next[left - 1];
            }
            if (needle[left] == needle[right]) {
                left++;
            }
            next[right] = left;
        }
    }

二拷姿、利用 next 數(shù)組進(jìn)行匹配

那么有同學(xué)就要問(wèn)了,得到這個(gè)前綴表旱函,到底對(duì)字符串匹配過(guò)程有什么幫助呢响巢?
別急,我們來(lái)看一個(gè)小例子棒妨。

  • 例:如果我們要從 aabaabaafa 中尋找 aabaaf 第一次出現(xiàn)的位置踪古。

1,暴力解法:兩個(gè)字符串均從第一個(gè)位置開(kāi)始比較,比較到第6位發(fā)現(xiàn) b 不等于 f 伏穆,比較失敗拘泞。長(zhǎng)串走到第二個(gè)位置;短串回到第一個(gè)位置枕扫,繼續(xù)比較直到長(zhǎng)串走完陪腌。( 時(shí)間復(fù)雜度O(m * n))
2,KMP算法:兩個(gè)字符串均從第一個(gè)位置開(kāi)始比較烟瞧,比較到第6位發(fā)現(xiàn) b 不等于 f偷厦,比較失敗。長(zhǎng)串位置不變燕刻;短串,調(diào)用 next 數(shù)組剖笙,找到 next 數(shù)組第5位的數(shù)字卵洗,并作為位置傳遞給短串,這時(shí)候短串到第3位 b 字符弥咪,和長(zhǎng)串匹配过蹂,比較繼續(xù)。(時(shí)間復(fù)雜度O(m))

匹配錯(cuò)誤時(shí)

只是再解釋一下匹配錯(cuò)誤時(shí)的情況聚至,第6位 f 匹配失敗酷勺,這時(shí)候第5位 next 數(shù)組的值為2,這說(shuō)明此時(shí)第4扳躬、5位和第1脆诉、2位相同,又因?yàn)榈?贷币、5位已經(jīng)和長(zhǎng)串匹配成功击胜,這說(shuō)明第1、2位也可以匹配成功役纹,因此直接從第三位偶摔,也就是數(shù)組下標(biāo)為2的地方開(kāi)始短串匹配即可。

匹配成功時(shí)

長(zhǎng)短串位置均正常遞增即可促脉,只是需要增加結(jié)束條件辰斋,即短串位置到達(dá)短串右端點(diǎn),這說(shuō)明長(zhǎng)串已經(jīng)有位置可以將短串完全匹配瘸味,循環(huán)結(jié)束宫仗。

我們可以發(fā)現(xiàn),匹配過(guò)程和 next 數(shù)組生成過(guò)程代碼幾乎完全一樣硫戈,因此代碼的思路模版可以進(jìn)行記憶背誦锰什。

  • 完整代碼:
        int n = 0;
        for (int h = 0; h < haystack.size(); h++) {
            while (n > 0 && haystack[h] != needle[n]) {
                n = next[n - 1];
            }
            if (haystack[h] == needle[n]) {
                ++n;
            }
            if (n == needle.size()) {
                return h - needle.size() + 1;
            }
        }
        return -1;

KMP完整代碼

class Solution {
public:
    void getNext(int* next, string needle) {
        int left = 0;
        next[0] = left;
        for (int right = 1; right < needle.size(); right++) {
            while (left > 0 && needle[left] != needle[right]) {
                left = next[left - 1];
            }
            if (needle[left] == needle[right]) {
                left++;
            }
            next[right] = left;
        }
    }

    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int n = 0;
        for (int h = 0; h < haystack.size(); h++) {
            while (n > 0 && haystack[h] != needle[n]) {
                n = next[n - 1];
            }
            if (haystack[h] == needle[n]) {
                ++n;
            }
            if (n == needle.size()) {
                return h - needle.size() + 1;
            }
        }
        return -1;
    }
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汁胆,更是在濱河造成了極大的恐慌梭姓,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫩码,死亡現(xiàn)場(chǎng)離奇詭異誉尖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)铸题,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)铡恕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丢间,你說(shuō)我怎么就攤上這事探熔。” “怎么了烘挫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵诀艰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我饮六,道長(zhǎng)其垄,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任卤橄,我火速辦了婚禮绿满,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窟扑。我一直安慰自己喇颁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布嚎货。 她就那樣靜靜地躺著无牵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厂抖。 梳的紋絲不亂的頭發(fā)上茎毁,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音忱辅,去河邊找鬼七蜘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛墙懂,可吹牛的內(nèi)容都是我干的橡卤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼损搬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碧库!你這毒婦竟也來(lái)了柜与?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嵌灰,失蹤者是張志新(化名)和其女友劉穎弄匕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沽瞭,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迁匠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驹溃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片城丧。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豌鹤,靈堂內(nèi)的尸體忽然破棺而出亡哄,到底是詐尸還是另有隱情,我是刑警寧澤布疙,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布磺平,位于F島的核電站,受9級(jí)特大地震影響拐辽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擦酌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一俱诸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赊舶,春花似錦睁搭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至寓调,卻和暖如春锌唾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夺英。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工晌涕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痛悯。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓余黎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親载萌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惧财,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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