字符串 | LeetCode 28 找出字符串中第一个匹配项的下标(kmp算法)

76 阅读5分钟

题目描述

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1

本题思路

KMP算法

在说明本题思路之前需要先将KMP算法弄明白,首先给出两个定义:

  • 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
  • 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。

KMP算法主要思想:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

haystack字符串(需要从其中找出字符串)称为主串,needle字符串(需要从主串中找出该字符串)称为模式串

最简单的的比较方式就是一一对比,从主串的开头比较一一与模式串对比,只要发现不同于主串,在对主串的第一个字符的下一个字符重新与模式串的第一个字符比较。直到对比模式串全都匹配成功为止。但是该种方法效率比KMP效率会低一些。

KMP算法是如果遇到不同的字符,就跳回一个已知且模式串部分与主串匹配的位置。

这个位置是哪里?

个人理解有误请指出,谢谢!

暂且不想具体实现,模式串查找到与主串不同的字符之前的字符串(不包括该字符)所对应的前缀,与 主串不同的字符之前(不包括该字符)的后缀。这里可能说的比较麻烦可能看不懂,我再解释一下。

简单来说就是,对比到两个字符串的位置包括该位置(分别将两个字符串分为两半),模式串前半部分的前缀(不包括查找到的不同字符)与主串前半部分的后缀(不包含查找到不同字符),两者的最长交集,称它为最长公共前后缀。那么最长相等的字符串第一个字符在主串中所对应的位置就是这个点,当然也可能没有。又因为两者之前的部分一定相同所以只需要利用模式串(因为模式串是固定的,一个模式串就可以对应一个next数组)的前半部分字符串来求出最长公共前后缀。(两个前半部分就体现了KMP的主要思想利用已知已经匹配的内容避免从头再去匹配。)

原理已经说清,那么next数组有什么作用,怎么初始化next数组?

对比过程中同时遍历主串和模式串,如果不匹配,跳到模式串上述字符所在的位置即next[i-1](如果对比不符的字符为模式串[i])。就可使主串已经遍历的后端与模式串已经遍历的前端最大程度的匹配,减少无用的匹配时间。

根据上面理解,其实直接利用模式串就可以得出next数组,为什么?

根据上述理解,,因为模式串和主串都不包括该字符,之前的字符都是相同的所以可以直接利用模式串来得出next数组。

next[i-1]表示什么?
  • 第一个理解:模式串[i]不匹配时需要跳回的位置
  • 第二个理解:模式串[0,i-1]的最长公共前后缀的长度

假设模式串的长度为i,next数组定义的长度与模式串相同。因为next[i-1](也就是next数组的最后一个数据)是模式串[i]不匹配时跳回的位置,而模式串最长为i,不可能匹配到模式串[i],所以个人认为next[i-1]没有什么实际意义,仅仅是利用第二个理解来定义next[i-1]。可以将next数组初始化后将最后一个值更改为其他值尝试,都可以通过。

next数组的初始化

具体实现

void getNext(int* next, const string& s) {
            int j = 0; // j表示要回退到的字符位置。
            next[0] = 0;
            for (int i = 1; i < s.size(); i++) {
                while (j > 0 && s[i] != s[j]) {//如果不匹配利用已知回退
                    j = next[j - 1];
                }
                if (s[i] == s[j]) {
                    j++;
                }
                next[i] = j;
            }
        }

两种情况

  • 字符串匹配:最长公共前后缀的长度加1,也表示不匹配时跳回的长度也可向后+1
  • 字符串不匹配:利用第二个理解,需要回退到next[j-1]

本题具体实现

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        vector<int> next(needle.size());
        getNext(&next[0], needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

本篇可能会有些说的不到位或者说是错误的,后续随着学习的加深会进行更改,也希望大家能指出我的错误。