【算法-字符串3】听说KMP很难?进来看这篇:实现strstr(),查找子串

今天,带来KMP算法的讲解。文中不足错漏之处望请斧正!

理论基础点这里


今天我们来实现strstr()。

题意转化

在一个字符串mainStr中找另一个字符串subStr。

解决思路

  • 两指针i和j分别在mainStr和subStr中拿取字符尝试匹配
    • 匹配:继续
    • 不匹配:
      • 暴力:i 回退,两串从头开始匹配 O(n*m)
      • KMP:i 不回退,两串从前面仍然匹配的位置继续匹配

暴力算法效率太低,一旦有不匹配的字符,i要回退到这次匹配的 i 的起始位置的下一个位置;j 要回退到子串起始位置。

在这里插入图片描述

更优的算法是KMP算法。

KMP算法

KMP是一种线性时间复杂度的字符串匹配算法,通过减少 i 和 j 的回退来提高效率。

算法思路如下:

  • 两指针 i 和 j 分别在 mainStr 和 subStr 中拿取字符尝试匹配
    • 匹配:两串的指针继续后移
    • 不匹配:两串的指针移动到 下次匹配时两串仍然匹配的部分 后面,准备进行下一次尝试匹配
    • j == subStr.size() 表示匹配成功

关于“仍然匹配的部分”,等会讲解,现在先看过程。

在这里插入图片描述

图中,两串已经进行了5个字符的匹配,在尝试匹配第6个时发现不匹配:KMP会两串的指针移动到 下次匹配时两串仍然匹配的部分 后面,继续尝试匹配。

在这里插入图片描述

怎么计算仍然匹配的部分

利用subStr已匹配部分的最长相等前后缀进行等量代换,找下次匹配两串仍然匹配的部分。

对于下图:

在这里插入图片描述

有:

  1. subStr的前缀aa == subStr的后缀aa (最长相等前后缀)
  2. subStr的前缀aa == mainStr的前缀aa (两串的这一部分已匹配)
  3. subStr的后缀aa == mainStr的后缀aa (两串的这一部分已匹配)

可得:

图中红色部分全部相等。

即:

mainStr后缀aa == subStr前缀aa,它们其实就是 下次匹配仍然匹配的部分。可以对照图仔细理解。

在这里插入图片描述

为什么要用最长的相等前后缀长度?

因为要尽可能利用已匹配字符来减少 j 的回退,毕竟能只退一步为什么要退两步?

那,如何获取最长相等前后缀长度?这就要引入next表了。

为什么用最长相等前后缀就能优化效率?

本质上,我们是利用 ”已匹配字符“ 来优化效率,最长相等前后缀只是我们优化的一种途径。最长相等前后缀利用已匹配字符的对称性,加上已匹配字符,代换出了 若排除不匹配字符,下一次尝试匹配还会有哪些部分仍然匹配。

大家好好把这个问题想明白,能很大程度帮助理解KMP,很多文章视频没有讲到这一点。

如何获取最长相等前后缀长度

要进行字符串匹配,两串在任意一个字符尝试匹配时都可能失败,那按照上面的说法,对于某字符串 str 的所有部分(也就是以任一字符结尾的子串),我们都需要计算它的最长相等前后缀长度。

并且,由于这个长度决定了 j 下一步如何回退,所以我们称存储 str 所有部分最长相等前后缀长度的表为 next表。

步骤如下:

  1. 两个指针 len 和 i 分别指向 前缀的末尾 和 后缀的末尾(len是前缀末尾的指针,+1就是前缀长度)
  2. 用 i 遍历 str,len 和 i 每次都要为 i 结尾的后缀找一个最长相等前缀
    1. 当前后缀末尾不等:前缀前面部分 + 前缀末尾 != 后缀前面部分 + 后缀末尾
      无法直接形成新的最长相等前后缀,所以我们退而求其次,让 len 回退,找更短的前缀,尝试和当前后缀匹配
      在这里插入图片描述
      解释:

      1. b前的a == c前的a 已匹配,是相同的(利用已匹配的字符等量代换)
      2. 对照 c前的a的最长相等前后缀长度,可知 subStr第一个a == c前的a
      3. 所以三者相等
      4. ac 无法匹配 ab,那根据 ac 中的a向前找到其最长相等前后缀 a,让len回退到next[len - 1],继续尝试匹配。回退是为了往回找更短的前缀,尝试匹配。
    2. 当前后缀末尾相等:前缀前面部分 + 前缀末尾 == 后缀前面部分 + 后缀末尾
      可以直接形成新的最长相等前后缀,++len,收获长度
      在这里插入图片描述

优化在哪了

  • i 不需要回退,减少整体匹配的次数
  • j 按策略回退,不用每次都匹配所有字符

总结思路

  1. 获取next表
  2. i 和 j 分别在 mainStr 和 subStr 中取字符尝试匹配
    1. 匹配:两串的指针继续后移
    2. 不匹配:两指针移动到下次匹配时,两串仍然匹配的部分后面
      1. 下次匹配时,有仍然匹配的部分:利用最长相等前后缀长度等量代换,找到下一次匹配仍然匹配的部分
      2. 下次匹配时,无仍然匹配的部分:没有优化空间,i后移,j置零,两串都回退,重新开始匹配
      3. 当 j == subStr.size() 表示匹配成功
  3. 跳出还没匹配成功,表示失败,返回-1

编程实现

class Solution {
public:
    int strStr(string mainStr, string subStr) {
        vector<int> next = getNext(subStr); // 获取next表
        int i = 0;
        int j = 0;
        
        while (i < mainStr.size()) { // i 和 j 分别在 mainStr 和 subStr 中取字符尝试匹配
            if (mainStr[i] == subStr[j]) { // 匹配:两串的指针继续后移
                ++i;
                ++j;
            } else { // 不匹配:指针移动到下次匹配时,两串仍然匹配的部分后面
                if (j > 0) { // 下次匹配时,有仍然匹配的部分
                    j = next[j - 1];
                } else { // 下次匹配时,无仍然匹配的部分
                    ++i;
                    j = 0;
                }
            }

            if (j == subStr.size()) {
                return i - j;
            }
        }

        return -1;
    }

private:
    vector<int> getNext(const string &str) {
        vector<int> next(str.size());
        int len = 0;
        int i = 1;

        next[0] = 0;

        while (i < str.size()) {
            while (len > 0 && str[i] != str[len]) { // len > 0 避免越界
                len = next[len - 1];
            }

            if (str[i] == str[len]) {
                ++len; // len指向前缀末尾,++就是前缀长度
            }

            next[i++] = len; // 收获当前的后缀的最长前后缀长度
        }

        return next;
    }
};
  • 时间复杂度: O(n + m)
  • 空间复杂度: O(m),next数组

今天的分享就到这里了,感谢您能看到这里。

这里是培根的blog,期待与你共同进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/239260.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML实现页面

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>工商银行电子汇款单</title> </head> &…

主机访问Android模拟器网络服务方法

0x00 背景 因为公司的一个手机app的开发需求&#xff0c;要尝试链接手机开启的web服务。于是在Android Studio的Android模拟器上尝试连接&#xff0c;发现谷歌给模拟器做了网络限制&#xff0c;不能直接连接。当然这个限制似乎从很久以前就存在了。一直没有注意到。 0x01 And…

回顾【数学基础】找出断层,继续前进, 使用chatGPT学习并解决实际问题:微积分

已经学过的算术、代数、几何。跳过。 从微积分开始 想象一下&#xff0c;你在画一条曲线&#xff0c;或者在一个大草地上奔跑。微积分就是一种数学工具&#xff0c;帮助我们了解这条曲线的形状&#xff0c;或者你奔跑的方式。 微分&#xff08;就像研究曲线上的每一小点&…

SQL基础理论篇(十一):事务隔离

文章目录 简介事务并发时的常见异常什么是脏读&#xff1f;什么是不可重复读&#xff1f;什么是幻读&#xff1f; 事务的常用隔离级别参考文献 简介 之前我们讲过事务的四大特性&#xff0c;即ACID&#xff0c;分别是原子性、一致性、隔离性和持久性。隔离性就是事务的基本特性…

ROBdispatch stage

ROB会跟踪所有pipeline中的指令的状态&#xff1b;一旦ROB中&#xff0c;header指的entry complete了&#xff0c;则该指令可以commit,其architectural state属于visible了&#xff1b;如果header instruction 发生了异常&#xff0c;pipleine需要flush, 在该exception instruc…

Python接口自动化测试 —— Requests库学习

安装&#xff1a; pip install requests 例子&#xff1a; import requests r requests.get(http://www.baidu.com) print r.status_code print type(r) print r.cookies运行程序&#xff0c;得到结果&#xff1a; 运行程序&#xff0c;得到结果&#xff1a; 200 <…

Leetcode—2963.统计好分割方案的数目【困难】

2023每日刷题&#xff08;五十七&#xff09; Leetcode—2963.统计好分割方案的数目 算法思想 参考灵神思路 实现代码 class Solution { public:long long mod 1e97;long long pow(long long x, int cnt) {if(cnt 0) {return 1;}if(cnt 1) {return x % mod;}long long …

css处理 纯英文数据不换行问题 - word-break、word-wrap

问题图 解决 添加 css 样式 word-break: break-all;补充 还有一个 word-wrap 样式&#xff0c;可以看下 参考 &#xff1a; word-wrap: normal 只在允许的断字点换行&#xff08;浏览器保持默认处理&#xff09;。word-wrap: break-word 在长单词或 URL 地址内部进行换行。

书-选择排序法P156

#include<stdio.h> int main(){int b[5]{8,2,6,3,7};int i , j ,k ;for(i0;i<4;i){for(ji1;j<5;j)if(b[i]<b[j]){kb[i];b[i]b[j];b[j]k;} }for(i0;i<5;i)printf("%d ",b[i]); return 0; }选择排序&#xff1a;就是自己跟下一个比较&#xff0c;然后…

Android studio 无法查看源码

Android studio 查看源码时提示 Decompiled .class file,bytecode version:52.0(java 8) 1、检查 buildToolsVersion 2、检查相关资源文件

SPRD Android 13 下拉状态栏菜单添加静音快捷键简单记录

SPRD Android 13 下拉状态栏菜单添加静音快捷键简单记录 需要修改文件具体修改补丁吐槽需要修改文件 frameworks/base/packages/SystemUI/res/values/config.xml frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java frameworks/base…

1844_高边驱动以及低边驱动的选择

Grey 全部学习内容汇总&#xff1a;GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1844_高边驱动以及低边驱动的…

mmpi量表在各企事业单位 入职体检中的应用

mmpi量表主要应用在医院精神科门诊中&#xff0c;用来检测筛查精神类疾病&#xff0c;比如&#xff1a;焦虑抑郁&#xff0c;疑病妄想强迫性、精神分裂、精神病态、社会内向性、癔症&#xff0c;精神衰弱&#xff0c;躁狂等等。 民航&#xff0c;司法&#xff0c;军警&#xf…

创建第一个SpringBoot项目

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 循序渐进学SpringBoot ✨特色专栏&…

点评项目——好友关注模块

2023.12.12 本章实现好友关注模块&#xff0c;包含以下功能的实现&#xff1a;关注与取关、共同关注、关注推送。 关注与取关 当点击某个用户的主页时&#xff0c;会调用如下接口&#xff1a; 该接口是用来判断是否已经关注该用户&#xff0c;最后一个参数是该用户的i…

1,使用IDLE开启我们第一个Python程序

前面我们已经安装好了Python&#xff0c;安装了Python后&#xff0c;他会自动帮我们安装一个IDLE。IDLE是一个Python自带的非常简洁的集成开发环境&#xff08;IDE&#xff09;。他是一个Python Shell&#xff0c;我们可以利用Python Shell与Python交互。下面我们就利用IDLE开发…

如何学习网络安全

我们应该怎么学习网络安全 首先&#xff0c;必须&#xff08;时刻&#xff09;意识到你是在学习一门可以说是最难的课程&#xff0c;是网络专业领域的顶尖课程&#xff0c;不是什么人、随随便便就能学好的。不然&#xff0c;大家都是黑客&#xff0c;也就没有黑客和网络安全的概…

【玩转TableAgent数据智能分析】TableAgent全功能详解及多领域数据分析实践(中)不同领域数据分析实践

3 电影点评数据分析实践 利用本身自带的电影点评数据&#xff0c;来具体看一下TableAgent的分析能力&#xff0c;选择电影点评数据&#xff0c;智能体会自动导入该数据DMSC20000.csv&#xff0c;大小为3.3 MB。在数据信息展示区&#xff0c;就会显示出该数据&#xff0c;并提供…

C/C++,图算法——Dinic最大流量算法

1 文本格式 // C implementation of Dinics Algorithm #include<bits/stdc.h> using namespace std; // A structure to represent a edge between // two vertex struct Edge { int v; // Vertex v (or "to" vertex) // of a directed edge u…

微服务实战系列之通信

前言 掰个指头数一数&#xff0c;博主的“微服务实战系列”从无到有&#xff0c;从零走到了十五。如果比作时钟&#xff0c;刚好走过了一刻度。 当初为什么要做这个系列&#xff0c;博主想了又想&#xff0c;私以为作为当下软件领域的几个“hot spot”之一&#xff0c;又乘着…