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

news2024/11/20 22:42:02

例题 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.coloradmin.cn/o/1416859.html

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

相关文章

Python实战:将爬虫获取到的数据存到数据库中

在前几篇 Python 实战中&#xff0c;我们直接把爬虫获取到的数据存储到 excel 文件或者 csv 文件中。今天&#xff0c;我们将爬虫获取到的数据存储到数据库中。 而存到数据库中&#xff0c;我们可以选择 MySQL、PostgreSQL、SqLite、Sql Server 等数据库。 在这些数据库中 Sq…

重装Windows系统出现Windows无法安装到这个磁盘,选中的磁盘采用GPT分区

文章目录 1.问题描述2.问题解决 1.问题描述 重装Windows系统时&#xff0c;出现Windows无法安装到这个磁盘&#xff0c;选中的磁盘采用GPT分区这个提示 2.问题解决 1.shiftF10&#xff0c;打开命令行 2.输入&#xff1a;diskpart (打开分区工具) 3.输入&#xff1a;list di…

C语言“%”运算符是否可以对小数进行运算?

一、问题 “&#xff05;”运算符是否可以对⼩数进⾏运算&#xff1f; 二、解答 “&#xff05;”运算符&#xff0c;称为求余运算符或者模运算符&#xff0c;要求“&#xff05;”两侧都为整型数据&#xff0c;否则将会产⽣错误 #include <stdio.h> int main() {floa…

电路笔记 :MOS场效应晶体管+红外遥控+AMS1117 电源模块

三极管&#xff08;BJT&#xff0c;Bipolar Junction Transistor&#xff09;和 MOSFET&#xff08;Metal-Oxide-Semiconductor Field-Effect Transistor&#xff09;是两种不同类型的晶体管&#xff0c;它们在工作原理、性能特性和应用方面有一些重要的区别。 结构和工作原理…

Qt项目文件以及对象树

"在哪里走散&#xff0c;你都会找到我~" 前篇&#xff0c;我们仅仅对Qt创建了第一个简单的项目。相比于使用其他IDE创建工程项目&#xff0c;Qt会为自动创建诸如&#xff1a;.pro、.h\.cpp、.iu等文件&#xff0c;这些文件到底是什么&#xff1f;我们在使用Qt时 应该…

欢迎来到 Greasy Fork,这里是一个提供用户脚本的网站。

官方地址&#xff1a; https://greasyfork.org/zh-CN 欢迎来到 Greasy Fork&#xff0c;这里是一个提供用户脚本的网站。 脚本适用于&#xff1a; baidu.com bilibili.com qq.com iqiyi.com sohu.com 更多… 用户脚本是什么&#xff1f; 用户脚本是一段代码&#xff0c;它们能…

前端——CSS

目录 文章目录 前言 一.CSS简介 1.CSS选择器 2.CSS选择器语法 3.CSS样式引入 4.CSS 高级选择器 二.CSS样式 1.字体 ​编辑 2.文本 3. 背景 4.边框 5.边距 6.浮动 7.清除浮动 8.定位 9. 列表样式 10.伪类样式 三.盒子模型 四.CSS3新特性 1.边框 2.盒子阴影 …

win11设置mysql开机自启

目录 命令式 1、打开命令提示符或 PowerShell&#xff1a; 2、使用管理员权限运行命令行工具&#xff1a; 3、设置 MySQL 服务为开机自启动&#xff1a; 4、启动 MySQL 服务&#xff1a; 5、 验证设置是否生效&#xff1a; 操作视图式 1、右击任务栏 ---> 选择任务管…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--强化学习、模仿学习、机器人、开放词汇

专属领域论文订阅 关注{晓理紫|小李子}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 分类: 大语言模型LLM视觉模型VLM扩散模型视觉…

经典目标检测YOLO系列(三)YOLOV3的复现(1)总体网络架构及前向处理过程

经典目标检测YOLO系列(三)YOLOV3的复现(1)总体网络架构及前向处理过程 和之前实现的YOLOv2一样&#xff0c;根据《YOLO目标检测》(ISBN:9787115627094)一书&#xff0c;在不脱离YOLOv3的大部分核心理念的前提下&#xff0c;重构一款较新的YOLOv3检测器&#xff0c;来对YOLOv3有…

网络安全科普:SSL证书保护我们的网上冲浪安全

当我们在线上愉快冲浪时&#xff0c;各类网站数不胜数&#xff0c;但是如何判定该站点是安全还是有风险呢&#xff1f; 当当当&#xff0c;SSL数字证书登场&#xff01;&#xff01; SSL证书也称为数字证书&#xff0c;是一种用于保护网站和用户之间通信安全的加密协议。由权…

SERVLET生命周期API

SERVLET生命周期API 在servlet的生命周期中,将发生创建Servlet上下文、创建会话、向Servlet上下文添加属性等各种事件。在servlet的生命周期内发生事件时,Web容器将通知侦听器类。要接收事件的通知,侦听器类需要扩展Servlet API的侦听器接口。 1. 事件类型 servlet生命周期…

DeepL 解除翻译文档的编辑限制

在使用DeepL官网翻译文档时&#xff0c;您可能会遇到无法编辑的问题。本文将详细介绍如何解除密码限制&#xff0c;使DeepL翻译文档变得可编辑。 第一步&#xff1a;文档翻译和下载 将需要翻译的文档上传至DeepL&#xff0c;进行整篇文档翻译。完成翻译后&#xff0c;下载翻译…

yum指令——Linux的软件包管理器

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 什么是软件包yum指令1.yum 是什么&#xff1f;2.Linux系统&#xff08;Centos&#xff09;的生态 3.yum的相关操作安装卸载yum的相关操作小结 软件源安…

[攻防世界]-Web:inget解析

查看网页 很明显&#xff0c;他是要我们输入id 这里我尝试了直接爆破&#xff0c;没有成功 解法一&#xff08;用万能密码&#xff09;&#xff1a; payload如下&#xff1a; 我这里还尝试了id1 or true # 没有成功&#xff0c;应该是题目把#给过滤了 解法二&#xff1a; …

【Javaweb程序设计】【C00165】基于SSM的高考志愿辅助填报系统(论文+PPT)

基于SSM的高考志愿辅助填报系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的高考志愿辅助填报系统 本系统分为前台系统模块、后台管理员模块以及后台学生模块 前台系统模块&#xff1a;当游客打开系统的网址后&…

网络编程套接字(2)

UDP数据报套接字编程 API介绍 DatagramSocket DatagramSocket是UDP的Socket,用于发送和接收数据报. 操作系统中有一类文件,就叫做socket文件(普通文件/目录文件:在硬盘上的) socket文件:抽象的表示了网卡这样的硬件设备 DatagramSocket就是对socket文件进行读写,也就是借助网…

【Java】Spring注解开发

一、Spring注解开发 1 注解开发定义Bean对象【重点】 目的&#xff1a;xml配置Bean对象有些繁琐&#xff0c;使用注解简化Bean对象的定义 问题导入 问题1&#xff1a;使用什么标签进行Spring注解包扫描&#xff1f; 问题2&#xff1a;Component注解和Controller、Service、R…

2024-1-26学习任务:堆实现算法和topK问题

前言 本文的学习任务&#xff1a;关于堆的实现以及相关的基础操作&#xff0c;包括向上调整算法和向下调整算法&#xff0c;同时利用该算法解决常见的topk问题&#xff0c;之后再对两种算法的时间复杂度进行分析&#xff0c;加深理解。 1.堆的实现 前面提到过&#xff0c;堆…

Jmeter学习系列之一:Jmeter的详细介绍

目录 一、Jmeter的介绍 二、Jemeter的特点 三、Jemter相关概念 3.1采样器&#xff08;Samplers&#xff09; 3.2逻辑控制器&#xff08;Logic Controllers&#xff09; 3.3监听器&#xff08;Listeners&#xff09; 3.4配置元件&#xff08;Configuration Elements&#…