C++刷题 -- KMP算法

C++刷题 – KMP算法

文章目录

  • C++刷题 -- KMP算法
    • 1.算法讲解
    • 2.算法实现


https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

1.算法讲解

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

前缀表(prefix table):
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

  • 举一个例子:
    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。请添加图片描述
    可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。
    但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

  • 前缀表是如何记录的:
    首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置
    前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

  • 最长公共前后缀:
    字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
    后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
    前缀表要求的就是相同前后缀的长度;
    字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。 等等…

  • 为什么一定要用前缀表
    刚刚匹配的过程在下标5的地方遇到不匹配,模式串是指向f,如图:请添加图片描述
    然后就找到了下标2,指向b,继续匹配:如图
    请添加图片描述
    下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
    所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。

  • 如何计算前缀表
    如图:
    请添加图片描述
    长度为前1个字符的子串a,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)
    请添加图片描述
    长度为前2个字符的子串aa,最长相同前后缀的长度为1;
    请添加图片描述
    长度为前3个字符的子串aab,最长相同前后缀的长度为0
    以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。
    那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
    请添加图片描述
    可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
    再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:
    请添加图片描述
    找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少,因为要找前面字符串的最长相同的前缀和后缀
    前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。
    最后就在文本串中找到了和模式串匹配的子串了。

  • 前缀表与next数组
    很多KMP算法的实现都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?
    next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
    其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)

2.算法实现

  • 使用next数组来匹配
    以下我们以前缀表统一减一之后的next数组来做演示。
    有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。
    注意next数组是新前缀表(旧前缀表统一减一了)。
    匹配过程动画如下请添加图片描述

  • 时间复杂度分析
    其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
    暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。
    为了和力扣题目28.实现strStr保持一致,方便大家理解,以下文章统称haystack为文本串, needle为模式串

  • 构造next数组
    定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。
    在这里插入图片描述
    构造next数组其实就是计算模式串s,前缀表的过程。 主要有如下三步:

    1. 初始化:
      定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置
      然后还要对next数组进行初始化赋值,如下:
      在这里插入图片描述
      j 为什么要初始化为 -1呢,因为之前说过 前缀表要统一减一的操作仅仅是其中的一种实现,我们这里选择j初始化为-1,下文我还会给出j不初始化为-1的实现代码。
      next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)
      所以初始化next[0] = j 。

    2. 处理前后缀不相同的情况:
      因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较
      所以遍历模式串s的循环下标i 要从 1开始,代码如下:
      在这里插入图片描述
      如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
      怎么回退呢?
      next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。
      那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
      在这里插入图片描述
      每次求的都是当前字符串开头到i之间的子串的最大公共前后缀
      因此j指向子串的一个前缀末尾位置,i指向子串的一个后缀末尾位置
      例如:
      在这里插入图片描述
      上图中判断的是aab这个子串的前后缀,显然没有相同的前后缀,因此next[i]为0;
      在这里插入图片描述
      上图判断的是aabaa这个子串的前后缀,j+1指向的是当前的前缀aa的末尾,i指向的是当前的后缀aa的末尾,显然前后缀末尾是一致的,因此next[i] = j = 1;

    3. 处理前后缀相同的情况:
      如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
      在这里插入图片描述

构建next数组的逻辑:
请添加图片描述

最后整体构建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数组来做匹配
    在文本串s里 找是否出现过模式串t。
    定义两个下标j 指向模式串起始位置i指向文本串起始位置
    那么j初始值依然为-1,为什么呢? 依然因为next数组里记录的起始位置为-1。
    i就从0开始,遍历文本串,代码如下:
    在这里插入图片描述
    接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。
    如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。
    代码如下:
    在这里插入图片描述
    如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:
    在这里插入图片描述
    如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
    本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
    那么使用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里出现了模式串treturn (i - t.size() + 1);}
}

KMP算法的整体代码:

class Solution {
public: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]}}int strStr(string haystack, string needle) {if (needle.size() == 0) {return 0;}int next[needle.size()];getNext(next, needle);int j = -1; // // 因为next数组里记录的起始位置为-1for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配j = next[j]; // j 寻找之前匹配的位置}if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动j++; // i的增加在for循环里}if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串treturn (i - needle.size() + 1);}}return -1;}
};
  • 时间复杂度: O(n + m)
  • 空间复杂度: O(m), 只需要保存字符串needle的前缀表

前缀表不减1

class Solution {
public:void getNext(int* next, const string& s){int j = 0;//字符串当前检查的前缀的末尾next[0] = j;//初始化for(int i = 1; i < s.size(); i++){//前后缀两个子串都是从大小为1的子串开始检查的//如果前缀后缀末尾不一致,就对j进行回退,看回退后能否匹配while(j > 0 && s[i] != s[j]){j = next[j - 1];//next中记录着已经检查到的相同前后缀的长度}//如果前后缀末尾一致,就对j++if(s[i] == s[j]){j++;}//当出现一直的前后缀末尾,就需要在next表中记录当前的相同前后缀长度next[i] = j;}}int strStr(string haystack, string needle) {if(needle.size() == 0){return -1;}int next[needle.size()];getNext(next, needle);int j = 0; // 指向needle的指针for(int i = 0; i < haystack.size(); i++){//判断当前两指针所指字符是否相等,不相等就回退jwhile(j > 0 && haystack[i] != needle[j]){j = next[j - 1];}//如果一致,就对j++if(haystack[i] == needle[j]){j++;}//如果j遍历到了needle的末尾,就说明存在子串if(j == needle.size()){return i - needle.size() + 1;}}return -1;}
};

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

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

相关文章

【CLion】使用CLion开发STM32

本文主要记录使用CLion开发STM32&#xff0c;并调试相关功能 使用的CLion版本&#xff1a;2023.3.1 CLion嵌入式配置教程&#xff1a;STM32CubeMX项目 |CLion 文档 (jetbrains.com) OpenOCD官网下载&#xff1a;Download OpenOCD for Windows (gnutoolchains.com) GNU ARM工…

lv12 linux 内核移植 10

目录 1 内核概述 1.1 内核与操作系统 1.2 Linux层次结构 1.3 Linux内核特点 2 Linux内核源码结构 2.1 Linux内核源码获取 2.2 源码结构 3 Linux内核移植 3.1 在 Linux 官网下载 Linux 内核源码&#xff08;这里我们下载 linux-3.14.tar.xz&#xff09; 3.2 拷贝内核源…

【Spark-ML源码解析】Word2Vec

前言 在阅读源码之前&#xff0c;需要了解Spark机器学习Pipline的概念。 相关阅读&#xff1a;SparkMLlib之Pipeline介绍及其应用 这里比较核心的两个概念是&#xff1a;Transformer和Estimator。 Transformer包括特征转换和学习后的模型两种情况&#xff0c;用来将一个DataFr…

支持向量机 支持向量机概述

支持向量机概述 支持向量机 Support Vector MachineSVM ) 是一类按监督学习 ( supervisedlearning)方式对数据进行二元分类的广义线性分类器 (generalized linear classifier) &#xff0c;其决策边界是对学习样本求解的最大边距超亚面 (maximum-margin hyperplane)与逻辑回归和…

Unity | Shader基础知识(第七集:案例<让图片和外部颜色叠加显示>)

目录 一、本节介绍 1 上集回顾 2 本节介绍 二、添加图片资源 三、 常用cg数据类型 1 float 2 bool 3 sampler 四、加入图片资源 五、使用图片资源 1 在通道里加入资源 2 使用图片和颜色叠加 2.1 2D纹理采样tex2D 2.2 组合颜色 六、全部代码 七、下集介绍 相关…

26 redis 中 replication/cluster 集群中的主从复制

前言 我们这里首先来看 redis 这边实现比较复杂的 replication集群模式 我们这里主要关注的是 redis 这边的主从同步的相关实现 这边相对比较简单, 我们直接基于 cluster集群模式 进行调试 主从命令同步复制 比如这里 master 是 redis_7002, slave 是 redis_7005 然后 这…

11.HarmonyOS鸿蒙app_page的显示跳转方法

11.HarmonyOS鸿蒙app_page的显示跳转方法&#xff0c;text文本触发点击事件 使用Intent和Operation对象 创建新项目后&#xff0c;再创建secondPageAbility ability_main.xml <?xml version"1.0" encoding"utf-8"?> <DirectionalLayoutxmlns:…

LeetCode(65)LRU 缓存【链表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; LRU 缓存 1.题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 k…

【ArkTS】路由传参

传参 使用router.pushUrl()&#xff0c;router.push()官方不推荐再使用了。 格式&#xff1a; router.pushUrl({url: 路由地址,params:{参数名&#xff1a;值} )跳转时需要注意路由表中是否包含路由地址。 路由表路径&#xff1a; entry > src > main > resources &g…

Python+pip下载与安装

Hi, I’m Shendi Pythonpip下载与安装 最近有识别图片中物体的需求&#xff0c;于是选用了TensorFlow&#xff0c;在一番考虑下&#xff0c;还是选择直接使用Python。 Python下载安装 直接在搜索引擎搜索Python或通过 https://www.python.org 进入官网 在 Downloads 处点击 Al…

Gin之GORM的表关联查询操作详解

前期工作&#xff1a; 先查看下要操作的两张表&#xff1a; carton carton_cate //关系如下&#xff1a; // 一个章节对应一个动漫&#xff08;一对一&#xff1b;两种方法&#xff1a;belong to&#xff1b;has one&#xff09; // 一个动漫可以对应多个章节&#xff08;一…

Eclipse_01_如何设置代码文件背景颜色为护眼沙绿色

设置方法 Window --> Preference 参考文档 参考文档 1

uni-app ucharts中饼图与圆环图区别

项目情况&#xff1a; uni-app的用于移动端H5项目&#xff0c;包使用uni_modules目录存放。 图表引用ucharts中的echarts配置的组件方式 区别1 饼图与圆环图在echarts使用的配置都是pie类型。但是配置raduis使用&#xff1a; radius: [40%, 70%] 区别2 组件type指明&#xf…

Linux 非阻塞网络IO模式

非阻塞网络IO模式介绍 当用户线程发起一个 read 操作后&#xff0c;并不需要等待&#xff0c;而是马上就得到了一个结果。如果结果是一个 error 时&#xff0c;它就知道数据还没有准备好&#xff0c;于是它可以再次发送 read 操作。一旦内核中的数据准备好了&#xff0c;并且又…

修复泰坦陨落2缺少msvcr120.dll的5种方法,亲测有效

游戏《泰坦陨落2》缺少msvcr120.dll的问题困扰着许多玩家。这个问题的主要原因可能是系统环境不完整、软件或游戏版本不匹配、DLL文件丢失或损坏以及杀毒软件误判等。msvcr120.dll是Microsoft Visual C 2013 Redistributable的一个组件&#xff0c;它包含了许多运行库文件&…

【百度PARL】强化学习笔记

文章目录 强化学习基本知识一些框架Value-based的方法Q表格举个例子 强化的概念TD更新 Sarsa算法SampleSarsa Agent类 On_policy vs off_policy函数逼近与神经网络DQN算法DQN创新点DQN代码实现model.pyalgorithm.pyagent.py总结&#xff1a;举个例子 实战 视频&#xff1a;世界…

苏宁开放平台API接口全攻略:掌握电商数据,提升业务效率

一、概述 苏宁平台提供了丰富的API接口&#xff0c;用于开发人员与平台进行交互&#xff0c;实现各种功能。本文将介绍苏宁平台API接口的基本概念、使用方法和注意事项&#xff0c;帮助开发人员更好地利用这些接口&#xff0c;提高开发效率和质量。 二、API接口介绍 商品详情…

html之CSS的高级选择器应用

文章目录 一、CSS高级选择器有哪些呢&#xff1f;二、高级选择器的应用1、层次选择器后代选择器子选择器相邻兄弟选择器通用兄弟选择器 2、结构伪类选择器&#xff08;不常用&#xff09;3、属性选择器E[attr]E[attrval]E[attr^val]E[attr$val]E[attr*val] 一、CSS高级选择器有…

excel该如何实现生成条形码/二维码?

如何在Excel中制作条形码/二维码&#xff1f; 1.首先&#xff0c;打开电脑上的Excel。进入后&#xff0c;在上方菜单栏中找到并点击“开发工具”。如果没有找到开发工具&#xff0c;就先点击“文件”&#xff0c;在弹出菜单中再点击“选项”。 2.打开Excel选项窗口后&#xff0…

【Flink-cdc-Mysql-To-Kafka】使用 Flinksql 利用集成的 connector 实现 Mysql 数据写入 Kafka

【Flink-cdc-Mysql-To-Kafka】使用 Flinksql 利用集成的 connector 实现 Mysql 数据写入 Kafka 1&#xff09;环境准备2&#xff09;准备相关 jar 包3&#xff09;实现场景4&#xff09;准备工作4.1.Mysql4.2.Kafka 5&#xff09;Flink-Sql6&#xff09;验证 1&#xff09;环境…