【刷题笔记】匹配字符串||KMP||动图解析||符合思维方式

news2024/9/28 21:29:03

找出字符串中第一个匹配项的下标

1 题目描述

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

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

2 思路

其实说白了,这道题的意义就是为了让我们理解KMP算法。

网上很多讲解一上来就解释next数组怎么构建的,如何计算前缀等。但是我认为最重要的还是先明白KMP算法在做什么。

如下图,图中有上下两个字符串,上串为haystack,下串为needle

在这里插入图片描述

在这里插入图片描述

右移:

在这里插入图片描述

提示:为了让大家清晰地看懂我要说什么,这里我先规定几个词的含义:
前缀和后缀:指的是在needle的子串(我们设置为part_needle=needle[:n])中,一个从头开始的字符子串a和一个与其相同的但是末尾是part_needle的末尾的字符子串b
即:
a=[part_needle[0], ..., part_needle[1], ..., part_needle[prefix_len-1]
其实本质上也是a=[needle[0], ..., needle[1], ..., needle[prefix_len-1]
b=[part_needle[-prefix_len]], part_needle[-(prefix_len - 1), ..., part_needle[-1]

💡:a==b

prefix_len就是前缀和后缀的长度,而且是子串part_needle满足条件的最大值
如果嫌麻烦可以看看上面的第一张图就一目了然了。

我们的目标是,当遇到不匹配的时候,能够让needle尽可能地右移动,而不是每次只移动一个位置。为什么?

我们先看一个动图:

在这里插入图片描述

我们这里将一开始的图进行了细化,可以看到,needlehaystack已经有8个字符完成了匹配(黄色和绿色字符),在第九个字符的时候,haystack是橘色,needle是红色,不匹配。我们可以看到,正常情况下的第一个想法是将needle后移一位,重新开始匹配。

此时,红色字符之前的八个字符已经构成了一个needle的子串part_needle=needle[:8]。假设我们已经找到了这个子串中最长的前缀和后缀(黄色部分),那么在前六步移动的时候,请仔细看紫色框里的字符串,这些字符串是不可能相等的,因为我们知道在前八个字符中,只有前两个字符构成的串和倒数后两个字符构成的串相等,那么我们可以直接跳过前六步,到达第七步。

注意,上面这段话的描述是KMP思想的关键,只有看懂了我上面说的什么,你才能知道KMP到底在干什么。请结合动图再看一遍,我希望我能够把我领悟到的东西让你也感受到。

那么我们现在的目标就是找出,needle的每个子串(注意都是从0号索引开始的子串)的前缀和后缀。比如needleleetcode,那么子串分别为:

l 第0个子串
le 第1个子串
lee 第2个子串
leet 第3个子串
leetc
leetco
leetcod

2.1 找到前缀长度

知道了子串是什么之后,我们要想找前缀和后缀,本质上就是在找前缀和后缀的长度。

现在让我们把目光只盯向needle
在这里插入图片描述

当我们到达一个未知子串的时候,该怎么找到未知子串中的前缀和后缀呢?

但是此时我已经有了前面所有子串的前缀和后缀,如果当前的字符(红色)和前缀后面的字符相等,如下图所示,
在这里插入图片描述

那么便可以确定当前的前缀和后缀了。

可是如果不等呢?是不是就说明当前未知子串没有前后缀?非也。我们换一种表示方式,不用圆圈表示字符了,只用颜色表示字符。

在这里插入图片描述

我们发现,虽然紫色和红色不相等,但是在黄色区域中,放大来看其实内有乾坤(黄色区域其实只是表示一个字符串),黄色区域也有前缀和后缀(蓝色部分),第一个黄色区域和第二个黄色区域具有量子纠缠的神奇性质,因为它们相等,所以第一个黄色区域的特征,第二个黄色区域也全都有。黄色区域的前缀蓝色,下一个字符是红色,正好跟我们的未知子串的最后一个字符一样是红色。

在这里插入图片描述

(不要考虑宽度,宽度在本小节的语境下没有意义)

这样我们知道了,如果我们的未知子串的最后一个字符和已知的上一个子串的前缀的后一个字符不相等,那么我们可以让未知字符的子串继续跟前缀的前缀的后一个字符去比较,一直迭代下去。直到找到相等字符或者某个前缀内部已经没有前缀了。

Wait,上面的叙述,好像就是动态规划吧。当前字符串的前后缀长度和上一个字符串有关。

我们设这个记录长度的数组为:

int[] prefix_len_dps = new int[needle.length()];
prefix_len_dps[0] = 0;

比如prefix_len_dps[2]就是记录的前三个字符构成的字符串的前后缀长度。

for (int i = 1; i < needle.length(); i++) {
    int prefix_len = prefix_len_dps[i - 1]; // 前一个子串的前后缀长度
    while (needle.charAt(prefix_len) != needle.charAt(i) && prefix_len > 0)
    // 首先我们要理解一点,长度可以用作下标,
    // needle.charAt(prefix_len)其实就是第i-1个子串前缀的后面一个字符
    // 如果needle.charAt(prefix_len) != needle.charAt(i),说明我们需要找前缀的前缀
    // 我们说过,prefix_len是第i-1个子串前缀的后面一个字符的位置,那么
    // prefix_len-1就是第i-1个子串的末尾的位置,
    // prefix_len_dps[prefix_len - 1]表示的就是第i-1个子串的前缀长度,即前缀的前缀的长度
    // 如果prefix_len==0,这说明第i-1个子串没有前缀,那么我们直接跳出循环,让第i个子串
    // 的第一个字符跟最后一个字符相比较
        prefix_len = prefix_len_dps[prefix_len - 1];
    if (needle.charAt(prefix_len) == needle.charAt(i)) prefix_len++;
    prefix_len_dps[i] = prefix_len;
}

2.2 找到匹配下标

int j = 0;
for (int i = 0; i < haystack.length(); i++) {
    while (haystack.charAt(i) != needle.charAt(j) && j > 0) {
        j = prefix_len_dps[j - 1] // 循环,找到第一个可以匹配的j
        // 其实这里的逻辑跟前面是一样的,我们期待接下来的i和j位置上的
        // 字符可以相等,如果不相等,就继续找其前缀的后一个位置,还不等,那就找前缀的前缀。。。
        // 一直到j==0,没有前缀了,跳出,然后直接比较
        // needle的第一个字符和haystack在i位置上的字符
    }
    if (haystack.charAt(i) == needle.charAt(j)) j++;
    if (j == needle.length()) return i - needle.length() + 1;
}

3 代码

class Solution {
    public int strStr(String haystack, String needle) {
        int[] prefix_len_dps = new int[needle.length()];
        prefix_len_dps[0] = 0;
        for (int i = 1; i < needle.length(); i++) {
            int prefix_len = prefix_len_dps[i - 1];
            while (needle.charAt(prefix_len) != needle.charAt(i) && prefix_len > 0)
                prefix_len = prefix_len_dps[prefix_len - 1];
            if (needle.charAt(prefix_len) == needle.charAt(i)) prefix_len++;
            prefix_len_dps[i] = prefix_len;
        }

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (haystack.charAt(i) != needle.charAt(j) && j > 0)
                j = prefix_len_dps[j - 1];
            if (haystack.charAt(i) == needle.charAt(j)) j++;
            if (j == needle.length()) {
                return i - needle.length() + 1;
            }
        }
        return -1;
    }
}

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

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

相关文章

Swift下如何使用#if条件编译

一、OC使用条件编译 OC中可以使用宏定义&#xff0c;再使用条件编译 #define USER_CUSTOM使用 #if USER_CUSTOM //其他代码 #endif二、Swift使用条件编译 Swift 不像ObjectC一样&#xff0c;通过定义一个变量&#xff0c;然后使用**#if #endif** 方法。swift需要设置一下才能…

SpringBoot错误处理机制(ControllerAdvice+ExceptionHandler自定义错误处理、默认机制源码分析、错误处理实战)

目录 1. SpringBoot自己对错误进行处理1.1 给一个Controller进行错误处理1.2 使用ControllerAdvice统一处理错误 2. 默认机制源码解析3. 错误处理机制实战 1. SpringBoot自己对错误进行处理 1.1 给一个Controller进行错误处理 使用ExceptionHandler&#xff0c;处理一个Conto…

【电源芯片】ZTP7193

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评百大…

销售流程中如何有效开发客户

在销售的海洋中&#xff0c;如何游刃有余地开发客户是一大关键。这需要深入了解你的目标客户&#xff0c;制定一份精细的销售计划&#xff0c;选择最合适的沟通方式&#xff0c;建立信任和信誉&#xff0c;并持续不断地跟进。 每一个潜在的客户都是一颗璀璨的星辰&#xff0c;…

地理坐标系转换

1.EPSG代码 搜索地理坐标系对应的EPSG代码 https://epsg.io/ 常用的地理坐标系EPSG代码&#xff1a; 2. 坐标系转换 转换网址&#xff1a; https://epsg.io/transform &#xff08;1&#xff09;修改 input coordinate system 和 output coordinate system&#xff0c; 可以…

【开源】基于Vue.js的农村物流配送系统的设计和实现

项目编号&#xff1a; S 024 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S024&#xff0c;文末获取源码。} 项目编号&#xff1a;S024&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2…

人工智能学习2(python数据清洗)

编译工具&#xff1a;PyCharm 一.数据清洗 转化数据类型、处理重复数据、处理缺失数据 import pandas as pddf pd.read_csv("/data.csv") df.sample(10) # 用于随机获取数据并返回结果 df.head(10) # 查看前十条数据 df.tail(10) # 查看后十条数据 df.shape …

Zookeeper 实战 | Zookeeper 和Spring Cloud相结合解决分布式锁、服务注册与发现、配置管理

专栏集锦&#xff0c;大佬们可以收藏以备不时之需&#xff1a; Spring Cloud 专栏&#xff1a;http://t.csdnimg.cn/WDmJ9 Python 专栏&#xff1a;http://t.csdnimg.cn/hMwPR Redis 专栏&#xff1a;http://t.csdnimg.cn/Qq0Xc TensorFlow 专栏&#xff1a;http://t.csdni…

Emeya散发的时代之光,似乎正盖过Panamera的余晖

路特斯Emeya的首秀象征着在超豪华汽车领域&#xff0c;纯电与超跑轿车的绝美融合正式拉开序幕。自由与灵感的碰撞&#xff0c;诗意与理想的贯通&#xff0c;汽车工业的性感似乎在Emeya上出现了全新的表达。 但短短不到一周的时间&#xff0c;我们又见到了全新Panamera的首秀。…

四招心理博弈术,让别人不敢欺负你,敬你三分

教你四招心理博弈术&#xff0c;让别人不敢欺负你&#xff0c;无论在职场中还是跟朋友交往中&#xff0c;为什么总是有人敢欺负你呢&#xff0c;是因为你不懂得人性博弈。其本质原因&#xff0c;是你做人没有边界感&#xff0c;或者没有原则&#xff0c;即便你有原则&#xff0…

【APUE】进程间通信

目录 一、管道 1.1 匿名管道 1.2 命名管道 二、XSI IPC 2.1 概述 2.2 消息队列 2.2.1 msgget 2.2.2 msgsnd 2.2.3 msgrcv 2.2.4 msgctl 2.2.5 代码示例 2.3 信号量数组 2.3.1 semget 2.3.2 semop 2.3.3 semctl 2.3.4 代码示例 2.3 共享内存 2.3.1 shmget…

服务器如何做好入侵防护

不管是企业还是个人&#xff0c;网上业务都需要依赖于服务器&#xff0c;服务器一旦被黑客入侵&#xff0c;企业会面临很多安全风险&#xff0c;比如业务被中断、数据被窃取、被加密勒索、服务器不稳定等影响。入侵防护&#xff0c;主机安全也是目前网络安全防护的一个重点。关…

碳酸氢锂/硫酸锂溶液纯化除钙镁解决方案

碳酸锂是锂电行业阳极生产中的一个重要原材料&#xff0c;主要用于制造钴酸锂、镍酸锂、锰酸锂等电极材料&#xff0c;也用于充电 锂电池中作非水溶液电解质等&#xff0c;具有良好的电化学性能&#xff0c;应用领域还在不断扩大。工业级碳酸锂主含量&#xff08;Li2CO3&#x…

idea创建spring boot项目,java版本只能选择17和21

1.问题描述 java版本为"11.0.20"&#xff0c;idea2023创建spring boot项目时&#xff08;File->Project->Spring Initializr&#xff09;&#xff0c;java版本无法选择11&#xff0c;导致报错&#xff0c;如下图所示&#xff1a; 2.原因 spring2.X版本在2023…

java设计模式学习之【抽象工厂模式】

文章目录 引言抽象工厂模式简介定义与用途实现方式&#xff1a; 使用场景优势与劣势抽象工厂模式在spring中的应用银行和贷款服务示例代码地址 引言 在我们之前的讨论中&#xff0c;我们探索了工厂方法模式——一种简化单一产品创建的设计模式。现在&#xff0c;我们将视角转向…

QT linux下应用程序打包

一、应用程序app 1、应用程序的pro文件 2、 程序工作函数 3、app的UI界面 二、动态库lib 1、Lib类头文件 2、.cpp文件 三、对应用程序和动态库进行构建 1、对动态库进行qmake,然后进行构建 2、对应用程序进行qmake&#xff0c;然后进行构建 3、查看构建目录 四、编写脚本 …

零基础学编程轻松学编程,分享一款中文编程工具,编程构件简介

零基础学编程轻松学编程&#xff0c;分享一款中文编程工具&#xff0c;编程构件简介 中文编程开发语言工具编辑区界面截图如上图。 给大家分享一款中文编程工具 零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#…

Mac苹果视频剪辑:Final Cut Pro Mac

Final Cut Pro是一款由Apple公司开发的专业视频非线性编辑软件&#xff0c;是业界著名的视频剪辑软件之一。它最初发布于1999年&#xff0c;是Mac电脑上的一款独占软件。Final Cut Pro具有先进的剪辑工具、丰富的特效和颜色分级、音频处理等功能&#xff0c;使得用户可以轻松地…

Java后端开发——MVC商品管理程序

Java后端开发——MVC商品管理程序 本篇文章内容主要有下面几个部分&#xff1a; MVC架构介绍项目环境搭建商品管理模块Servlet代码重构BaseServlet文件上传 MVC 是模型-视图-控制器&#xff08;Model-View-Controller&#xff09;&#xff0c;它是一种设计模式&#xff0c;也…

打破限制!MySQL 5.7至8.0跨版本迁移,1分钟搞定多版本数据迁移

在上个月&#xff0c;MySQL 5.7 正式结束了生命周期&#xff0c;即EOL&#xff08;End of Life&#xff09;&#xff0c;意味着Oracle将不再为 MySQL 5.7 提供技术支持&#xff0c;包括Bug修复或安全漏洞&#xff0c;大大增加了使用数据库的风险。在全球关系型数据库市场中&…