日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

每天一道LeetCode-----KMP算法查找子串,重新实现strStr()函数

發布時間:2024/4/19 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 每天一道LeetCode-----KMP算法查找子串,重新实现strStr()函数 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Implement strStr()

原題鏈接Implement strStr()


子串查找,方法很多,可以用string內置的接口find解決,這里主要復習一下kmp算法


kmp算法常用于字符串匹配,相比于傳統方式一個一個查找,當遇到不匹配時從頭開始的土方法,kmp可以有效減少比較次數,遇到不匹配時,不需要從頭開始

判斷abababc中是否存在ababc子串,直觀上是存在的
abababc為源字符串source,ababc為目標字符串target
傳統方式

0 1 2 3 4 5 6 source下標 a b a b a b c source a b a b c target 0 1 2 3 4 target下標

依次比較source[0]和target[0],source[1]和target[1]…source[4]和target[4]
當比較到source[4]和target[4]時,發現字符a和字符c不相等,此時需要從頭第二個字符開始

0 1 2 3 4 5 6 source下標 a b a b a b c sourcea b a b c target0 1 2 3 4 target下標

比較source[1]和target[0]時就不相等,此時需要從第三個字符開始

0 1 2 3 4 5 6 source下標 a b a b a b c sourcea b a b c target0 1 2 3 4 target下標

依次比較source[2]和target[0],source[3]和target[1]…source[6]和target[4]
發現終于相等了,說明有子串存在,但是每次失配后都從下一個字符開始從頭查找,每次都需要將target所有字符都比較一次,效率堪憂啊…


kmp算法

0 1 2 3 4 5 6 source下標 a b a b a b c source a b a b c target 0 1 2 3 4 target下標

依次比較source[0]和target[0],source[1]和target[1]…source[4]和target[4]
當比較source[4]和target[4]時不相等,于是…

0 1 2 3 4 5 6 source下標 a b a b a b c sourcea b a b c target0 1 2 3 4 target下標

直接比較source[4]和target[2],發現相等,繼續比較source[5]和target[3]…source[6]和target[4],發現存在子串
在比較時,發現target的前兩個字符ab在第二次開始時根本沒有比較,是直接從第三個字符開始的。

直觀的看,當比較source[4]和target[4]時,source[0 : 3]肯定和target[0 : 3]相等,也就是說比較a和c時前面4個字符abab是匹配的,又因為target[0 : 1]和target[2 : 3]相等,那么和target[2 : 3]匹配的source[2 : 3]肯定也和target[0 : 1]匹配,那么就可以直接將target后移兩位,讓source[2 : 3]匹配target[0 : 1],從target[2]開始比較,也就直接讓source[4]和target[2]比較


所以,在失配時移動target而非source,這就需要對target進行預處理以便在失配時直到該移動多少,kmp將預處理后的數據存放在一個prefix數組中,這里成為前綴數組,首先理解什么是字符串中每個字符的前綴
假設當前字符為target[j],存在i < j滿足target[0 : i] == target[j - i : j],此時target[0 : i]為字符target[j]的前綴,而i + 1為target[j]的前綴個數
比如說ababaca字符串

target[0] == 'a',第一個字符,前綴個數是0,前綴為空 target[1] == 'b',沒有一個i滿足target[0 : i] == target[1 - i : 1],前綴個數是0,前綴為空 target[2] == 'a',target[0 : 0] == target[1: 1],所以i為0,此時前綴個數為1,前綴為"a" target[3] == 'b',target[0 : 1] == target[2 : 3],所以i為0,此時前綴個數為2,前綴為"ab" target[4] == 'a',target[0 : 2] == target[2 : 4],所以i為2,此時前綴個數為3,前綴為"aba" target[5] == 'c',沒有一個i滿足target[0 : i] == target[5 - i : 5],此時前綴個數為0,前綴為空 target[6] == 'a',target[0 : 0] == target[6 : 6],所以i為0,此時前綴個數為1,前綴為"a"

在對目標字符串預處理時,本質就是計算每個字符的前綴個數,前綴的意思在于,若target[i]的前綴個數是n,那么有target[0 : n - 1] == target[i - n + 1, i],也就是說如果target[i+1]和souce[j]比較時失配,可以直接將target[0 : n - 1]移動到target[i - n + 1, i]的位置,而下次開始直接從target[n]和source[j]開始比較。
對于最開始的那個例子,abababc為源字符串source,ababc為目標字符串target ,匹配之前需要對target進行預處理,得到每個字符對應的前綴個數

0 1 2 3 4 下標 a b a b c target 0 0 1 2 0 前綴個數

接著開始依次比較

0 1 2 3 4 5 6 source下標 a b a b a b c source a b a b c target 0 1 2 3 4 target下標 0 0 1 2 0 前綴個數

當比較到失配時,這里source[4] != target[4]導致失配,找到target[4 - 1] 即target[3]的前綴個數2,也就是說target[0 : 1] == target[2 : 3],所以可以直接將target[0 : 1]移動到target[2 : 3]的位置

0 1 2 3 4 5 6 source下標 a b a b a b c sourcea b a b c target 0 1 2 3 4 target下標a b a b c target0 1 2 3 4 target下標0 0 1 2 0 前綴個數

下面接著比較target[2]和source[4]即可,這樣,失配時一次性移動了2步,而不需要每次都只移動1步,當字符串很大時,target重復字符過多,前綴個數比較大時,效果更加明顯
代碼如下

class Solution { public:int strStr(string haystack, string needle) {if(needle.size() == 0)return 0;std::vector<int> prefix(needle.size(), 0);handlePrefix(prefix, needle);/* * 和生成prefix的過程很像* 這里lp記錄著needle當前遍歷到的位置* 如果不相等,就改變lp,把needle[lp-1]的前綴個數的那么多元素后移到lp前面*/int lp = 0;for(int i = 0; i < haystack.size(); ++i){while(lp > 0 && haystack[i] != needle[lp])lp = prefix[lp - 1];if(haystack[i] == needle[lp])++lp;if(lp == needle.size())return i - lp + 1;}return -1;} private:void handlePrefix(std::vector<int>& prefix, string needle){/** lp* 記錄著上一個字符needle[i-1]的前綴個數。* 假設needle[i-1]的前綴個數是n,那么needle[0, n-1] == needle[i-1-n : i-1]* 那么對于needle[i]而言,因為needle[i-1]的前綴個數是n,到達needle[0, n-1],* 那么,首先它的前綴個數的很可能是n+1,所以直接比較needle[i]和needle[lp]* lp是個數,對應下標剛好是第lp + 1個字符* 如果不相等,就需要減小前綴個數,根據needle[lp-1]的前綴個數重復進行*/int lp = 0;/* i記錄著當前遍歷的目標字符串的位置 */for(int i = 1; i < needle.size(); ++i){while(lp > 0 && needle[i] != needle[lp])lp = prefix[lp - 1];if(needle[i] == needle[lp])++lp;prefix[i] = lp;}} };

總結

以上是生活随笔為你收集整理的每天一道LeetCode-----KMP算法查找子串,重新实现strStr()函数的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。