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,一经查实,立即删除!

相关文章

【>D:\10\Debug\RCa00828(34): fatal error RC1022: expected ‘#endif‘】

1>D:\10\Debug\RCa00828(34): fatal error RC1022: expected ‘#endif’ The error message you’re seeing, fatal error RC1022: expected ‘#endif’, indicates that the resource compiler encountered an issue when processing a resource script file (typically w…

【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工…

大话前端:WebAssembly的未来与前端开发

大话前端&#xff1a;WebAssembly的未来与前端开发 引言 近年来&#xff0c;WebAssembly&#xff08;简称Wasm&#xff09;作为一种新兴的网络技术&#xff0c;正在逐步改变前端开发的格局。作为一种低级的类汇编语言&#xff0c;它允许代码以接近原生的速度运行&#xff0c;…

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 拷贝内核源…

React中对UI组件化的步骤和设计原理

在 React 中&#xff0c;对 UI 进行组件化是一个将复杂界面拆分成更小、独立和可复用部分的过程。这种方法有助于提高应用的可维护性、可测试性&#xff0c;并且可以提升开发效率。下面是 React 中对 UI 组件化的步骤和设计原理的详细解释&#xff1a; 步骤一&#xff1a;识别…

uniapp组件和周期的知识点以及怎么使用

UniApp是一个使用Vue.js开发跨平台应用的框架&#xff0c;它可以将代码编译成微信小程序、支付宝小程序、H5、App等多个平台的应用。在UniApp中&#xff0c;组件是构建应用界面的基本单位&#xff0c;而生命周期则是组件在实例化、挂载、更新和销毁过程中的一系列方法。 以下是…

【Spark-ML源码解析】Word2Vec

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

Unity触摸 射线穿透UI解决

unity API 之EventSystem.current.IsPointerOverGameObject() 命名空间 &#xff1a;UnityEngine.EventSystems 官方描述&#xff1a; public bool IsPointerOverGameObject(); public bool IsPointerOverGameObject(int pointerId); //触摸屏时需要的参数&#xff…

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

支持向量机概述 支持向量机 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…

C#动态加载第三方非托管DLL,LoadLibraryEx,LoadLibrary

C#动态加载第三方DLL 当我们需要加载第三方非托管DLL时&#xff0c;通常会直接使用DllImport的方式&#xff0c;代码如下&#xff1a; [DllImport("GetFile.dll", CallingConvention CallingConvention.StdCall, CharSet CharSet.Unicode)] static extern string…

【华为OD题库-095】字符串划分-Java

题目 给定一个小写字母组成的字符串S&#xff0c;请找出字符串中两个不同位置的字符作为分割点&#xff0c;使得字符串分成三个连续子串且子串权重相等&#xff0c;注意子串不包含分割点。 若能找到满足条件的两个分割点&#xff0c;请输出这两个分割点在字符串中的位置下标&am…

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;一…

Flink-状态后端

状态后端是一个“开箱即用”的组件&#xff0c;可以在不改变应用程序逻辑的情况下独立配置。 Flink中提供了两类不同的状态后端&#xff0c;一种是“哈希表状态后端”&#xff08;HashMapStateBackend&#xff09;&#xff0c;另一种是“内嵌RocksDB状态后端”&#xff08;Embe…

42道CSS高频题整理(附答案背诵版)

1、简述CSS3选择器优先级及计算&#xff1f; CSS的选择器优先级是一个相对复杂的概念&#xff0c;它规定了在一组样式冲突时&#xff0c;哪些样式将被浏览器采纳。选择器优先级是通过一个四位的值来计算的&#xff0c;形式为&#xff1a;[内联样式, ID选择器, 类选择器/属性选…