简洁思路推理 KMP 算法——子字符串匹配

例题 28. 找出字符串中第一个匹配项的下标

暴力遍历解法

枚举原串 ss 中的每个字符作为「发起点」,每次从原串的「发起点」和匹配串的「首位」开始尝试匹配: 匹配成功:返回本次匹配的原串「发起点」。 匹配失败:枚举原串的下一个「发起点」,重新尝试匹配。

时间复杂度: O(mn)

空间复杂度: O(1)

class Solution {
public:int strStr(string s, string p) {int n = s.size(), m = p.size();for(int k = 0; k <= n - m; k++){int i, j = 0; while(j < m and s[k + i] == p[j]){j++; i++;}if(j == m) return i + k;}return -1;}
};

KMP

还有一种专门的方案KMP,时间复杂度O(m+n)。

很多讲解方案尤其复杂,这里只需要简单的理解一个东西就豁然开朗原理,无需背代码,在应试时候可以现场推导原理。

字符串前缀与后缀

首先理解字符串的前缀和后缀。

例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。

要注意的是,字符串本身并不是自己的前缀或者后缀。

部分匹配表(Partial Match Table)

前面介绍字符串的前缀和后缀有什么用呢?是因为KMP算法的核心数据结构部分匹配表PMT用到了字符串的前缀集合与后缀集合。

首先给出一个定义1PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。(请读三遍理解并深刻记住这个定义)

以下讲解将使用例子:主字符串ababababca中查找模式字符串abababca

例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。

再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。所以对于”ababa”而言,它在PMT表中对应的值就是1。

这里也先看一个PMT表关于模式字符串abababca``的例子(这里暂时不需要理解,看一下数据结构长什么样子)。

在这里插入图片描述

接下来就是KMP算法的核心:如何利用定义1简化暴力双循环的搜索方法:

  • 在暴力双循环中定义了i j 指针分别指向待匹配字符串str匹配字符串match ,若位置str[i+k] 和 match[j] 匹配失败时,丢弃已匹配成功的 match[0,j)字符串
  • 注意到已匹配成功的match[0,j)字符串其实是有用的,这里可以进行优化。
  • 目前匹配的字符串中, 从str[k, i+k) 得到后缀集合postfix_str, 从match[0,j) 得到前缀集合prefix_match,然后求两个集合的交集中最长的字符串 sub_str
  • 关键原理1:这个字符串 sub_str就是暴力双循环从 i+1 位置重新遍历到当前 i+j 位置时候匹配得到的最长字符串。
    • 以下图为例,a中在j = 6 处匹配失败,那么如果暴力循环就是i 回到k,j 回到0重新开始匹配,最终i又走到6这个位置的时候,最长匹配的一次是str[2,6]的字符串
    • KMP的前后缀集合重合的最长字符串,就是意味着在待匹配字符串str 中从i 回溯到k位置的后缀中与匹配字符串match匹配上最长的一次。
      在这里插入图片描述

有了如上的发现可以快速的回溯指针并省略一些遍历,但怎么快速的匹配前缀与后缀呢。下面关键原理2将解释如何做到这个快速回溯。

  • 关键原理2:可以发现match在j位置匹配失败了,match[0,j) 与 str[k,k+i)是完全相同的,则后缀集合postfix_str与前缀集合prefix_match实际都可以从 match[0,j) 数组来得到 。而match数组是遍历前就确定的,则遍历之前就可以通过match数组得到这个快速回溯的地址。
    • 看到这,你就明白了KMP的核心数据结构 部分匹配表PMT ,按照这个思路求出例子中的PMT表

    • 在这里插入图片描述

    • 其中 **next 数组就是在当前位置匹配失败后可以直接将j指针跳到哪个位置,代表 str[k, k+i) 和 match[0,j) 又匹配上了。**其中 -1 是第一个位置都匹配不上,i j都需要移动,而其他的都是匹配失败后只移动了j到next[j]

具体程序为:

int KMP(string haystack, string needle){int n = haystack.size(), m = needle.size();vector<int> next = getNext(needle);// ij两个指针分别指向被匹配字符串和模式字符串的已成功匹配位置int i = 0, j = 0; // 遍历整个被匹配字符串比对 或 匹配成功 结束循环while (i < n && j < m){// 初始匹配位置 或 当前指针位置匹配成功if (j == -1 || haystack[i] == needle[j]){j++;i++;}else j = next[j];}if (j == m) return i - j;return -1;}

好了,KMP的主体原理就讲完了。这里你会发现KMP的核心思想就是从前后缀集合最长公共字符串而来。

Next 数组快速推导

另一方面首先需要得到PMT 中的核心数据结构Next数组,这里也有一定的技巧帮助我们快速得到。

具体来说,就是从模式字符串的第一位开始对自身进行匹配运算(注意,不包括第0位,因为第0位就匹配失败的next值为-1)。 在任一位置,能匹配的最长长度就是当前位置的next值。

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在next数组求取的时候,其实就是以match[0,i]的字符串后缀与match[0,j]的字符串前缀匹配。注意:那么当匹配失败可以快速的使用next数组,相当于递归的使用了KMP。

求next数组值的程序如下所示:

vector<int> getNext(string needle){int n = needle.size();vector<int> next(n + 1); // +1 防止越界next[0] = -1; // 第一个匹配不成功指针j返回初始位置int post_idx = 0; // 后缀字符串指针int pre_idx = -1; // 匹配上的前缀字符串指针// 移动后缀指针while(post_idx < n){// 判断与前缀的重合// 从头匹配的时候或当前位置匹配成功就记录if (pre_idx == -1 || needle[pre_idx] == needle[post_idx]){// 注意首先post_idx++,因为下一个位置的next是当前位置的KMP值// pre_idx++ 是因为KMP值不是从0计数,前后缀子集重合长度是从1计算pre_idx++; post_idx++; next[post_idx] = pre_idx;}else{// 若当前位置未匹配上,就可以进入KMP匹配返回// 注意这里相当于递归了KMP的使用pre_idx = next[pre_idx];}}return next;}

参考

https://www.zhihu.com/question/21923021/answer/281346746

https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solutions/575568/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/

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

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

相关文章

UI自动化搭建背景及优劣势分析

经常有人会问&#xff0c;什么样的项目才适合进行UI自动化测试呢&#xff1f;UI自动化测试相当于模拟手工测试&#xff0c;通过程序去操作页面上的控件。而在实际测试过程中&#xff0c;经常会遇到无法找到控件&#xff0c;或者因控件定义变更而带来的维护成本等问题。 哪些场…

Docker—入门及Centos7安装

1、Docker入门 1.1、Docker是什么&#xff1f; Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build&#xff0c;Ship&#xff0c;and Run Any App,Anywhere”&#xff0c;也就是通过对应组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&…

docker环境搭建及其安装常用软件

centos安装docker Install Docker Engine on CentOS | Docker Docs 下载docker sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io…

MongoDB本地部署并结合内网穿透实现公网访问本地数据库

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 4. 结语 前言 MongoDB是一个基于分布式文件…

TCP 三次握手 四次挥手以及滑动窗口

TCP 三次握手 简介&#xff1a; TCP 是一种面向连接的单播协议&#xff0c;在发送数据前&#xff0c;通信双方必须在彼此间建立一条连接。所谓的 “ 连接” &#xff0c;其实是客户端和服务器的内存里保存的一份关于对方的信息&#xff0c;如 IP 地址、端口号等。 TCP 可以…

【c语言】详解操作符(下)

前言&#xff1a; 在上文中&#xff0c;我们已经学习了 原码、反码、补码、移位 操作符、移位操作符、位操作符、逗号表达式、下标访问[ ]、函数调用&#xff08; &#xff09;&#xff0c;接下来我们将继续学习剩下的操作符。 1. 结构成员访问操作符 1.1 结构体成员的直接访…

台式电脑的ip地址在哪里找

在网络连接方面&#xff0c;IP地址是非常重要的信息&#xff0c;它是用于标识网络设备的唯一地址。对于台式电脑用户来说&#xff0c;了解自己设备的IP地址是非常有必要的&#xff0c;因为它可以帮助解决网络连接问题&#xff0c;进行远程访问和共享文件等功能。本文将指导读者…

深度学习知识

context阶段和generation阶段的不同 context阶段&#xff08;又称 Encoder&#xff09;主要对输入编码&#xff0c;产生 CacheKV(CacheKV 实际上记录的是 Transformer 中 Attention 模块中 Key 和 Value 的值&#xff09;&#xff0c;在计算完 logits 之后会接一个Sampling 采…

幻兽帕鲁服务器多少钱?4核16G支持32人在线吗?

4核16G服务器是幻兽帕鲁Palworld推荐的配置&#xff0c;阿里云和腾讯云均推出针对幻兽帕鲁的4核16G服务器&#xff0c;阿里云4核16G幻兽帕鲁专属服务器32元1个月、66元3个月&#xff0c;腾讯云4核16G14M服务器66元1个月、277元3个月、1584元一年。云服务器吧yunfuwuqiba.com分享…

vue笔记(一):部署

首 文档 安装 1、安装nodejs&#xff08;链接&#xff09;&#xff0c;18.0以上版本。 2、在想要创建项目的目录下执行命令 npm create vuelatest 按提示创建项目&#xff0c;其中vue router是实现路由功能&#xff0c;pinia实现组件之间共享数据。如果项目需要两个功能建…

python-自动化篇-运维-监控-简单实例-道出如何使⽤Python进⾏网络监控?

如何使⽤Python进⾏⽹络监控&#xff1f; 使⽤Python进⾏⽹络监控可以帮助实时监视⽹络设备、流量和服务的状态&#xff0c;以便及时识别和解决问题。 以下是⼀般步骤&#xff0c;说明如何使⽤Python进⾏⽹络监控&#xff1a; 选择监控⼯具和库&#xff1a;选择适合⽹络监控需…

蓝桥杯省赛无忧 课件49 DFS-剪枝

01 数字王国之军训排队 02 特殊的三角形 03 特殊的多边形

优雅的python(二)

&#x1f308;个人主页&#xff1a;小田爱学编程 &#x1f525; 系列专栏&#xff1a;c语言从基础到进阶 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取更多关于c语言的优质内容&#xff01;&#x1f3c6;&#x1f3c6; &#x1f600;欢迎来到小田代码世界~ &#x…

【docker】linux系统docker的安装及使用

一、docker应用的安装 1.1 安装方式 Docker的自动化安装&#xff0c;即使用提供的一键安装的脚本&#xff0c;进行安装。 官方的一键安装方式&#xff1a;curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 国内 daocloud一键安装命令&#xff1a;curl -s…

二刷代码随想录|Java版|回溯算法1|回溯基础理论+组合问题

理论 写链表之类的真的很痛苦&#xff0c;赶紧跳到回溯&#xff01;这次我想结合算法设计这本书&#xff0c;把java版写出来。放在第三部分吧。希望能够在研一完成这项工作&#xff01; 从一刷总结以下的几个要点&#xff1a; 回溯方法模板性非常强&#xff01;&#xff01;可…

企业虚拟机服务器中了lockbit3.0勒索病毒怎么办,lockbit3.0勒索病毒解密处理流程

对于企业来说&#xff0c;企业的数据是企业的核心命脉&#xff0c;关乎着企业的生产与运营的所有工作。随着网络技术的不断发展&#xff0c;网络安全威胁也在不断增加。近期&#xff0c;云天数据恢复中心接到了很多企业的求助&#xff0c;企业的虚拟机服务器遭到了lockbit3.0勒…

Unity MonoBehaviour 生成dll

dllllllllllllll&#x1f953; &#x1f959;vs创建类库项目&#x1f9c0;添加UnityEngine、UnityEditor引用&#x1f355;添加MonoBehaviour类&#x1f9aa;设置dll生成路径&#x1f37f;生成dll&#x1f354;使用dll中的Mono类 &#x1f959;vs创建类库项目 &#x1f9c0;添加…

Nginx进阶篇【五】

Nginx进阶篇【五】 八、Nginx实现服务器端集群搭建8.1.Nginx与Tomcat部署8.1.1.环境准备(Tomcat)8.1.1.1.浏览器访问:8.1.1.2.获取动态资源的链接地址:8.1.1.3.在Centos上准备一个Tomcat作为后台web服务器8.1.1.4.准备一个web项目&#xff0c;将其打包为war8.1.1.5.启动tomcat进…

基于GPT3.5逆向 和 本地Bert-Vits2-2.3 的语音智能助手

文章目录 一、效果演示二、操作步骤三、架构解析 一、效果演示 各位读者你们好&#xff0c;我最近在研究一个语音助手的项目&#xff0c;是基于GPT3.5网页版的逆向和本地BertVits2-2.3 文字转语音&#xff0c;能实现的事情感觉还挺多&#xff0c;目前实现【无需翻墙&#xff0…

ubuntu20.04 安装ROS2 记录

主要参考B站古月居的ROS2入门21讲 和 以下链接&#xff08;基本和视频上一致&#xff09; ubuntu20.04安装ROS2 详细教程_ubuntu20.04 ros2-CSDN博客 但是中间有些需要注意的地方&#xff0c; 1&#xff0c;添加源 步骤中提到 sudo curl -sSL https://raw.githubuserconten…