代码随想录刷题|LeetCode KMP算法理论 28. 实现 strStr() 459.重复的子字符串

简介: 代码随想录刷题|LeetCode KMP算法理论 28. 实现 strStr() 459.重复的子字符串

KMP算法理论

说在前面


先定义几个标识:


文本串:对应力扣中的haystack,长的那个字符串,遍历文本串本文使用指针 i

模式串:对应力扣中的needle,短的那个字符串,遍历模式串本文使用指针 j

a needle in a haystack 在草垛中的针

首先得明白几个问题,带着这几个问题去理解KMP算法


什么是KMP算法?

用于字符串匹配的一种算法

为什么要用KMP算法?

一般来说,如果要看两个字符串匹配,使用的是暴力循环,从第一个字符开始匹配,如果不匹配就从第二个字符进行匹配。时间复杂度是O(n*m)

但是KMP算法可以不回退文本串的指针进行匹配,时间复杂度是O(n)

KMP算法可以当出现字符串不匹配的情况的时候,可以知道一部分之前已经匹配的文本内容,利用这些信息避免"模式串"从头再去做匹配

怎么知道之前文本匹配过的内容,并准确找到位置呢?

next数组

next数组是什么? ——》是一个前缀表

next数组用来干什么的? ——》 当出现字符匹配失败的时候,模式串可以跳过的匹配的字符个数

KMP算法的基本思路?

当我们发现某个字符不匹配的时候,由于已经知道之前遍历过的字符,利用这些信息避免暴力算法中的“回退”步骤

最长相同前后缀?

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

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


前缀表要求的就是相同前后缀的长度


KMP算法的思路


我们复盘暴力算法:是怎么进行匹配的?

       从文本串的第一个字符开始进行比较,如果存在不符合的字符,就打断匹配,文本串指针回退到<本次开始匹配的后一位>(因为这一轮开始的已经不行了),重新模式串从模式串第一个字符进行匹配,直到模式串每个字符都可以匹配上了,再返回结果,时间复杂度O(n*m)

       比如,下图中,i 指针指向文本串的4时,j 指向模式串的 4时,发现不匹配,i 就回退到 1 ,j就回退到 0,重新开始进行比较


c1feb60c383b4743884774fee2b6252e.png


那KMP算法会怎么做呢?

       KMP算法在匹配失败的时候,会去看最后一个匹配的字符所对应的next数组,查看这个数,模式串的前几位(next数组对应的数)就不用再进行匹配了

       以下图举例:也是 i 从0 开始,j 从 0 开始两个字符串进行第一轮匹配,两个指针都走到4的时候,字符不匹配,此时:模式串去查找最后一个匹配的字符对应的next数组,是2,那这代表着从文本串当前的位置开始,不再和模式串的前两个字符进行对比了,让指针 j 指向跳过模式串前两个字符的位置,而这个位置刚好是模式串的索引为2的位置,开始继续进行匹配

       整个过程中 i 指针没有发生过回退,只有指针 j 进行回退,按照next数组进行回退

       文本串的 i 永远不递减,这是KMP算法的精髓

af5edd4a8ac142d9a737deedc81d2f2b.png


所以接下来要做的就是:


了解为什么next数组可以做到这一点

怎么得到“模式串”的next数组

怎么使用next数组与“文本串”进行匹配


next数组(前缀表)


next数组代表了在匹配失败时,模式串中可以跳过匹配的字符个数,也是模式串的指针要回退到的索引位置

       next数组具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力


       为什么next数组可以做到这点?让模式串准确得跳过这么多字符串呢?

       以下图为例:next数组的本质其实就是寻找模式串中“相同的前后缀的长度”,并且一定是最长的前后缀,而且前后缀不能是字符串本身。所以说不匹配的时候,跳过的字符是最长的前缀,因为上一轮最长后缀已经给你试过水了。也可以说这一轮跳过匹配的这几个字符在上一轮匹配过了。

       模式串下标4之前的这部分的字符串(ABAB)的最长相等的前缀和后缀字符串 是子字符串AB,因为找到了最长相等的前后缀,匹配失败的位置就是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了(也就是从模式串的ABAB的最长前缀AB后面,下标2的地方开始就可以了)

       模式串与next数组对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。代码随想录


534824e966c543319cf1a86bc260d06e.png


        指针 j 根据模式串已经有的next数组来进行回退,避免重复的运算,再想一下KMP算法的原理: KMP算法在匹配失败的时候,会去看最后一个匹配的字符所对应的next数组,查看这个数(保存了最长前后缀的长度),然后让模式串的前几位跳过匹配,就是说:下一轮,我看了最后这几位匹配的有这么几位(最长后缀),你再来匹配这几位(对应的最长前缀)就不用走啦


       知道了为什么next数组可以做到精准回退,接下来就要看看怎么获得每个字符对应的最长相同前后缀?


       需要的前提:

       1、模式串s

       2、next[]数组 -----> 用来存储对应的前后缀


       要做三件事:

       1、初始化

       2、前后缀不相同,j指针回退

       3、前后缀相同,更新next数组的值


       首先:定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置(如下图所示),同时,j也代表最长前缀的长度

       对指针和next数组进行初始化:


int j = 0; 
next[0] = j; // 字符串第一个字符对应的最长前后缀长度为0
for (int i = 0; i < s.length(); i++) {
}

939dc53af8184057803d3fba660a94a3.png

 遇到前后缀不同的情况:

               遇到前后缀不相同的时候,j 指针就找前一位需要回退的位置进行回退,而且回退到下一个目标的时候,如果不符合要求,要继续进行回退,所以这里应该使用while


while (j > 0 && s.charAt(j) != s.charAt(i)) {
    j = next[j - 1];
}

    遇到前后缀相同的情况:
               
遇到前后缀相同的情况,此时 i 对字符串的元素下标和对next数组的元素下标指针是对应的,所以先对 j 进行++, 赋值给 next[i] , 然后 i 再向后走(for循环自然++)

if (s.charAt(j) == s.charAt(i)) {
    j++;
}
next[i] = j;

 总结在一起:

public void getNext(int[] next,String s) {
        // 初始化
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {
            // 前后缀不相同的情况
            while (j > 0 && s.charAt(j) != s.charAt(i)) {
                j = next[j - 1];
            }
            // 前后缀相同的情况
            if (s.charAt(j) == s.charAt(i)) {
                j++;
            }
            // 对next数组进行赋值
            next[i] = j;
        }
    }

使用next数据进行匹配

       上面得到了next数组,那怎么使用next数组进行匹配呢?

       定义两个下标,j 指向模式串起始位置;i 指向文本串起始位置,开始遍历文本串

int j = 0; // 因为next数组里记录的起始位置是0
for (int i = 0; i < haystack.length(); i++) {
}

  开始对needle.charAt(j)和haystack.charAt(i)进行比较,如果needle.charAt(j) != haystack.charAt(i),j就要从next数组里寻找下一个匹配的位置


while (j > 0 && needle.charAt(j) != haystack.charAt(i)) {
    j = next[j - 1];
}


 如果needle.charAt(j) == haystack.charAt(i),i 和 j 同时向后移动


1. if (needle.charAt(j) == haystack.charAt(i)) {
2.     j++; // i在for循环里面增加
3. }


怎么判断文本串中出现了字符串呢?


1. if (j == needle.length()) {
2. return i - needle.length() + 1;
3. }

       

  总结在一起:

public int strStr(String haystack, String needle) {
    // 使用next数据进行匹配
    if (needle.length() == 0) {
        return 0;
    }
    // 创建next数组
    int[] next = new int[needle.length()];
    // 调用方法填充next数组
    getNext(next,needle);
    int j = 0; // 因为next数组里记录的起始位置为0
    for (int i = 0; i < haystack.length(); i++) {
        while (j > 0 && needle.charAt(j) != haystack.charAt(i)) {
            j = next[j - 1];
        }
        if (needle.charAt(j) == haystack.charAt(i)) {
            j++;
        }
        if (j == needle.length()) {
            return i - needle.length() + 1;
        }
    }
    return -1;
 }


       参考资料:


28. 实现 strStr()

题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/

思路


实现 strStr()

暴力算法

       可以让字符串needle每次从haystack中得第i个字符进行匹配,所以遍历haystack,限制条件是从haystack中得第i个字符开始后面得长度还要够字符串needle的长度,要不然就没有意义了


然后开始对needle进行匹配,如果出现不匹配的情况,就将flag设置成false,再打断循环,因为出了循环要看看匹配的这个字符串有没有出现不匹配的情况

class Solution {
    public int strStr(String haystack, String needle) {
        int x = haystack.length();
        int y = needle.length();
        // 遍历haystack字符串
        for (int i = 0; i + y <= x; i++) {
            boolean flag = true;
            for (int j = 0; j < y; j++) {
                if (haystack.charAt(i + j) != needle.charAt(j)) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                return i;
            }
        }
        return -1;
    }
}

KMP算法

class Solution {
    public int strStr(String haystack, String needle) {
        // 使用next数据进行匹配
        if (needle.length() == 0) {
            return 0;
        }
        // 创建next数组
        int[] next = new int[needle.length()];
        // 调用方法填充next数组
        getNext(next,needle);
        int j = 0; // 因为next数组里记录的起始位置为0
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) {
                j = next[j - 1];
            }
            if (needle.charAt(j) == haystack.charAt(i)) {
                j++;
            }
            if (j == needle.length()) {
                return i - needle.length() + 1;
            }
        }
        return -1;
    }
    // 构造needle的前缀表(next数组)
    public void getNext(int[] next,String s) {
        // 初始化
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {
            // 前后缀不相同的情况
            while (j > 0 && s.charAt(j) != s.charAt(i)) {
                j = next[j - 1];
            }
            // 前后缀相同的情况
            if (s.charAt(j) == s.charAt(i)) {
                j++;
            }
            // 对next数组进行赋值
            next[i] = j;
        }
    }
}

459.重复的子字符串

题目链接:https://leetcode.cn/problems/repeated-substring-pattern/

思路

   看了一下题解,还没详细拆解

重复的子字符串

移动匹配

KMP算法

字符串匹配算法

常见的字符串匹配算法包括

  • 暴力匹配
  • Knuth-Morris-Pratt 算法
  • Boyer-Moore 算法
  • Sunday 算法
相关文章
|
9天前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
20天前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
21 3
|
19天前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
1月前
|
算法
两个字符串匹配出最长公共子序列算法
本文介绍了最长公共子序列(LCS)问题的算法实现,通过动态规划方法求解两个字符串的最长公共子序列,并提供了具体的编程实现细节和示例。
77 1
两个字符串匹配出最长公共子序列算法
|
1月前
|
存储 缓存 算法
如何通过优化算法和代码结构来提升易语言程序的执行效率?
如何通过优化算法和代码结构来提升易语言程序的执行效率?
|
1月前
|
搜索推荐
插入排序算法的讲解和代码
【10月更文挑战第12天】插入排序是一种基础的排序算法,理解和掌握它对于学习其他排序算法以及数据结构都具有重要意义。你可以通过实际操作和分析,进一步深入了解插入排序的特点和应用场景,以便在实际编程中更好地运用它。
|
25天前
|
缓存 分布式计算 监控
优化算法和代码需要注意什么
【10月更文挑战第20天】优化算法和代码需要注意什么
18 0
|
1月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
14 0
|
1月前
|
算法
第四章 KMP算法理论基础
第四章 KMP算法理论基础
18 0
|
1月前
|
算法
KMP算法
KMP算法
27 0