Files
leetcode-master/problems/0028.实现strStr().md
youngyangyang04 d5a824d43e Update
2020-09-05 20:35:47 +08:00

6.5 KiB
Raw Blame History

题目地址

https://leetcode-cn.com/problems/implement-strstr/

思路

本题是KMP 经典题目。

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

如果对KMP理论基础还不够了解的同学请看

定义一个函数getNext来构建next数组 函数参数为指向next数组的指针和一个字符串。 然后在函数里完成对next数组的构建。

这其实就是计算模式串s前缀表的过程。 主要有如下三步:

  1. 初始化

  2. 处理前后缀不相同的情况

  3. 处理前后缀相同的情况

  4. 初始化:

定义两个指针i和jj指向前缀终止位置严格来说是终止位置减一的位置i指向后缀终止位置与j同理

然后还要对next数组进行初始化赋值如下

        int j = -1;
        next[0] = j;

j 为什么要初始化为 -1呢因为之前说过 前缀表要统一减一的操作所以j初始化为-1。

next[i] 表示 i包括i之前最长相等的前后缀长度其实就是j

所以初始化next[0] = j 。

  1. 处理前后缀不相同的情况

因为j初始化为-1那么i就从1开始进行s[i] 与 s[j+1]的比较。

所以遍历模式串s的循环下表i 要从 1开始代码如下

        for(int i = 1; i < s.size(); i++) {

如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回溯。

怎么回溯呢?

next[j]就是记录着j包括j之前的子串的相同前后缀的长度。

那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值就是next[j])。

所以,处理前后缀不相同的情况代码如下:

while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
    j = next[j]; // 向前回溯
}
  1. 处理前后缀相同的情况

如果s[i] 与 s[j + 1] 相同那么就同时向后移动i 和j 说明找到了相同的前后缀同时还要将j前缀的长度赋给next[i], 因为next[i]要记录相同前后缀的长度。

代码如下:

if (s[i] == s[j + 1]) { // 找到相同的前后缀
    j++;
}
next[i] = j;

最后整体构建next数组的函数代码如下

    void getNext(int* next, const string& s){
        int j = -1;
        next[0] = j;
        for(int i = 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数组的逻辑流程如下

得到了next数组之后就要用这个来做匹配了。

在文本串s里 找是否出现过模式串t。

定义两个下表j 指向模式串起始位置i指向文本串其实位置。

那么j初始值依然为-1为什么呢 依然因为next数组里记录的起始位置为-1。

i就从0开始遍历文本串代码如下

for (int i = 0; i < s.size(); i++) 

接下来就是 s[i] 与 t[j + 1] 因为j从-1开始的 经行比较。

如果 s[i] 与 t[j + 1] 不相同j就要从next数组里寻找下一个匹配的位置。

代码如下:

while(j >= 0 && s[i] != t[j + 1]) {
    j = next[j];
}

如果 s[i] 与 t[j + 1] 相同那么i 和 j 同时向后移动, 代码如下:

if (s[i] == t[j + 1]) {
    j++; // i的增加在for循环里
}

如何判断在文本串s里出现了模式串t呢如果j指向了模式串t的末尾那么就说明模式串t完全匹配文本串s里的某个子串了。

本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始)所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

代码如下:

if (j == (t.size() - 1) ) {
    return (i - t.size() + 1);
}

那么使用next数组用模式串匹配文本串的整体代码如下

        int j = -1; // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
            while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (s[i] == t[j + 1]) { // 匹配j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
                return (i - t.size() + 1);
            }
        }

此时所有逻辑的代码都已经写出来了,本题整体代码如下:

C++代码

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

        int j = -1;
        for (int i = 0; i < haystack.size(); i++) {
            while(j >= 0 && haystack[i] != needle[j + 1]) {
                j = next[j];
            }
            if (haystack[i] == needle[j + 1]) {
                j++;
            }
            if (j == (needle.size() - 1) ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

更过算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。