1. 程式人生 > 其它 >leetcode28.實現strStr() (暴力解法&KMP演算法)

leetcode28.實現strStr() (暴力解法&KMP演算法)

題目要求實現strstr()函式,即輸入兩個字串str1和str2,找出str2在str1中出現的第一個位置,輸出下標,若str1中不存在str2作為子字串,則返回-1。函式如下:

int strStr(string haystack, string needle);
  • 解法一:暴力法

這裡首先想到的是使用暴力遍歷:用兩層for迴圈,比較str1中有沒有str2,若有返回開始的下標,無則返回-1。程式碼如下:

int strStr(string haystack, string needle) {
        //總體思路是先在總串上匹配字串,不行則在總串上後移
        int
n = haystack.size(), m = needle.size(); for (int i = 0; i + m <= n; i++) { // 設定跳出條件需要注意 bool flag = true; for (int j = 0; j < m; j++) { if (haystack[i + j] != needle[j]) { // 此處haystack[i + j]設定較為巧妙 flag = false;
break; } } if (flag) { return i; } } return -1; }

  具體思路和需要注意的點在註釋中已經寫清楚。

  • 解法二:KMP演算法

  KMP演算法是發明這個演算法的三位大佬名字首字母的縮寫,沒有別的特殊含義。KMP演算法的功能是匹配字串,但是時間複雜度會比我們的暴力解法低,怎麼實現的呢?

  這裡先貼一個大佬的詳細講解「程式碼隨想錄」KMP演算法詳解 - 實現 strStr() - 力扣(LeetCode) (leetcode-cn.com)

,具體需要注意的地方我會再補充。

  關鍵點:

  ①字串字首的定義:不包含最後一個字元的所有以第一個字元開頭連續子串。

  對於字串"aabaaf",其字首為:"a","aa","aab","aaba","aabaa"。

  同理,字串字尾的定義與此類似:不包含第一個字元的所有以最後一個字元結尾連續子串

  ②字首表的作用:用來回退的,當模式串與主串不匹配的時候模式串應該從哪裡開始重新匹配。如果是暴力匹配,就需要從頭開始匹配了;而使用字首表,會得到下一步匹配中,模式串應該跳到哪個位置。

  ③字首表的定義:記錄下標i之前(包括i)的字串中,有多大長度的相同字首字尾(上文連結中有詳細說明,不再贅述)

  依然是字串”aabaaf“,對以第一個字母開頭且其長度為1的子串"a",最長相同前後綴為0(因為”a“前後綴都為空);對於”aa“,其最長前後綴長度為1(字首”a“,字尾”a“):對於”aab“,最長相同前後綴長度為0;對於”aaba“,最長相同前後綴長度為1(字首”a“和字尾”a“);對於”aabaa“,最長相同前後綴長度為2;對於”aabaaf“,最長相同前後綴長度為0。

  由上一段的推理,得到一個數組[0, 1, 0, 1, 2, 0],即為字串”aabaaf“的字首表。

  ④next字串的獲取:KMP演算法需要使用next陣列實現退回操作,此處使用字首表統一減一得到next陣列,即[-1, 0, -1, 0, 1, -1]。(不同next陣列求法不同,但殊途同歸,不做深究)

  ⑤在C++程式中實現next陣列的獲取:

voidgetNext(int*next,conststring&s){ //傳入next陣列和字串s  
   //這裡使用j表示字尾末尾,i表示字首末尾 intj=-1; next[0]=j; for(inti=1;i<s.size();i++) { // 注意i從1開始 while(j>=0&&s[i]!=s[j+1]){ // 前後綴不相同了 j=next[j]; // 向前回溯 } if(s[i]==s[j+1]){ // 找到相同的前後綴 j++; } next[i]=j; // 將j(字首的長度)賦給next[i] } }

  ⑥通過上一步得到的next陣列來做匹配

int strStr(string haystack, string needle) {
    if (needle.size() == 0) {
        return 0;
    }
    int next[needle.size()];
    getNext(next, needle);
    int j = -1; // // 因為next數組裡記錄的起始位置為-1
    for (int i = 0; i < haystack.size(); i++) { // 注意i就從0開始
        while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
            j = next[j]; // j 尋找之前匹配的位置
        }
        if (haystack[i] == needle[j + 1]) { // 匹配,j和i同時向後移動
            j++; // i的增加在for迴圈裡
        }
        if (j == (needle.size() - 1) ) { // 文字串s裡出現了模式串t
            return (i - needle.size() + 1);
        }
    }
    return -1;
}