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

news2024/11/22 13:19:11

今天,带来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.coloradmin.cn/o/1305130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis Bitmaps 数据结构模型位操作

Bitmaps 数据结构模型 Bitmap 本身不是一种数据结构&#xff0c;实际上它就是字符串&#xff0c;但是它可以对字符串的位进行操作。 比如 “abc” 对应的 ASCII 码分别是 97、98、99。对应的二进制分别是 01100010、01100010、01100011, 如下所示&#xff1a; a b …

java--认识Stream

1.什么是Stream ①也叫Stream流&#xff0c;是JDK8开始新增的一套API(java.util.stream.*)&#xff0c;可以用于操作集合或者数组的数据。 ②优势&#xff1a;Stream流大量的结合了Lambda的语法风格来编程&#xff0c;提供了一种更加强大&#xff0c;更加简单的方式操作集合或…

利用anaconda中的Conda创建虚拟环境

目录 1. Anaconda 环境变量手动设置(详细)2. Conda 创建虚拟环境参考文献 1. Anaconda 环境变量手动设置(详细) 问题 Win键r打开运行对话框&#xff0c;输入cmd回车 输入conda&#xff0c;显示&#xff1a;‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处…

孩子还是有一颗网安梦——Bandit通关教程:Level 9 → Level 10

&#x1f575;️‍♂️ 专栏《解密游戏-Bandit》 &#x1f310; 游戏官网&#xff1a; Bandit游戏 &#x1f3ae; 游戏简介&#xff1a; Bandit游戏专为网络安全初学者设计&#xff0c;通过一系列级别挑战玩家&#xff0c;从Level0开始&#xff0c;逐步学习基础命令行和安全概念…

MistralAI发布全球首个MoE大模型-Mixtral 8x7B,创新超越GPT-4

引言 MistralAI&#xff0c;一家法国的初创企业&#xff0c;近期在AI界引发了轰动&#xff0c;刚刚发布了全球首个基于MoE&#xff08;Mixture of Experts&#xff0c;混合专家&#xff09;技术的大型语言模型——Mistral-8x7B-MoE。这一里程碑事件标志着AI技术的一个重要突破…

数据库中常用的锁

目录 1、数据库中常用的锁类型 2、常见的数据库 3、以MySQL为例 3.1 MySQL的事务 3.2 MySQL事务的四大特性 1. 原子性&#xff08;Atomicity&#xff09; 2. 一致性&#xff08;Consistency&#xff09; 3. 隔离性&#xff08;Isolation&#xff09; ⭐mysql中的事务隔…

【QT 5 调试软件+(Linux下验证>>>>串口相关初试串口)+Windows下qt代码在Linux下运行+参考win下历程+基础样例】

【QT 5 调试软件Linux下验证>>>>串口相关初试串口参考win下历程基础样例】 1、前言2、实验环境3、先行了解4、自我总结-win下工程切到Linux下1、平台无关的代码&#xff1a;2、依赖的库&#xff1a;3、文件路径和换行符&#xff1a;4、编译器差异&#xff1a;5、构…

simulinkveristandlabview联合仿真环境搭建

目录 开篇废话 软件版本 明确需求 软件安装 matlab2020a veristand2020 R4 VS2017 VS2010 软件安装验证 软件资源分享 开篇废话 推免之后接到的第一个让人难绷的活&#xff0c;网上开源的软件资料和成功的案例很少&#xff0c;查来查去就那么几篇&#xff0c;而且版本…

链表OJ—环形链表的约瑟夫问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#xff0c;一种是正在努力学习编程的你!一个爱学编程的人。各位看官&#xff0c;我衷心的希望这篇博客能对你…

使用Retrofit实现文件的上传和下载

一、前言 使用Retrofit实现文件的上传和下载&#xff0c;代码是正确的代码但是我也不知道为什么运行不出来。 报错内容可以给你们看一下暂时没有解决。 1.文件的上传报错内容 什么添加读写权限&#xff0c;降低目标sdk的版本都试过了不行。有木有会的留个言。 2.文件的下载…

PyQt6 水平布局Horizontal Layout (QHBoxLayout)

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计41条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

输出网络结构图,mmdetection

控制台输入&#xff1a;python tools/train.py /home/yuan3080/桌面/detection_paper_6/mmdetection-master1/mmdetection-master_yanhuo/work_dirs/lad_r50_paa_r101_fpn_coco_1x/lad_r50_a_r101_fpn_coco_1x.py 这个是输出方法里面的&#xff0c;不是原始方法。 如下所示&a…

无参数RCE知识点

什么是无参数RCE&#xff1f; 无参rce&#xff0c;就是说在无法传入参数的情况下&#xff0c;仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果 核心代码 if(; preg_replace(/[^\W]\((?R)?\)/, , $_GET[code])) { eval($_GET[code]); } 这段代码的核心就是只…

gamit一(虚拟机启动不了)

Intel VT-x处于禁用状态怎么办-百度经验 1重新启动电脑 2找到电脑对应的品牌&#xff0c;联想G510是F2, 3进去BIOS&#xff0c;configure里面修改virtual为enable&#xff0c;回车 4F10保存&#xff0c;退出

centos7上安装mysql5.7

1 下载mysql5.7网址 下载后缀名为“.tar.gz”的压缩包 连接虚拟机后 输入&#xff1a; rz 找到你下载的压缩包 2 解压缩 tar -zxvf mysql-5.7.26-linux-glibc2.12-x86_64.tar.gz将减压后的文件移动到/usr/local文件夹下并重命名为mysql mv mysql-5.7.26-linux-glibc2.12-x8…

极简壁纸js逆向(混淆处理)

本文仅用于技术交流&#xff0c;不得以危害或者是侵犯他人利益为目的使用文中介绍的代码模块&#xff0c;若有侵权请练习作者更改。 之前没学js&#xff0c;卡在这个网站&#xff0c;当时用的自动化工具&#xff0c;现在我要一雪前耻。 分析 第一步永远都是打开开发者工具进…

【精选】设计模式——工厂设计模式

工厂设计模式是一种创建型设计模式&#xff0c;其主要目的是通过将对象的创建过程封装在一个工厂类中来实现对象的创建。这样可以降低客户端与具体产品类之间的耦合度&#xff0c;也便于代码的扩展和维护。 工厂设计模式&#xff1a; 以下是Java中两个常见的工厂设计模式示例…

5G下行链路中的MIMO

5G MIMO 影响5G MIMO配置的主要因素是天线的数量和层数UE和gNB有一些预定义的表来定义天线端口和层的数量&#xff0c;选择了特定的表&#xff0c;UE如何确定表中的哪一行用于gNB的每次传输DCI 1-1中该规定了Antenna port 和 层数DMRS 端口数表示正在使用的天线数量&#xff0…

搭建商城系统的构架如何选择?

近期有很多网友在csdn、gitee、知乎的评论区留言&#xff0c;搭建商城系统是选择单体架构还是微服务架构&#xff0c;这里先说结论&#xff0c;如果是纯电商的话&#xff0c;商城系统的架构建议选择单体架构。我们分析下微服务和单体架构的优劣势&#xff0c;就知道了。 一、什…