字符串中第一个匹配的下标[简单]

优质博文:IT-BLOG-CN

一、题目

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

示例 1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:sad在下标06处匹配。
第一个匹配项的下标是0,所以返回0

示例 2:
输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:leeto没有在leetcode中出现,所以返回-1

1 <= haystack.length, needle.length <= 104
haystackneedle仅由小写英文字符组成

二、代码

暴力解法: 我们可以让字符串needle与字符串haystack的所有长度的子串均匹配一次。为了减少不必要的匹配,我们每次匹配失败即立刻停止当前子串的匹配,对下一个子串继续匹配。如果当前子串匹配成功,我们返回当前子串的开始位置即可。如果所有子串都匹配失败,则返回−1

class Solution {public int strStr(String haystack, String needle) {if (haystack.length() < needle.length()) {return -1;}// 注意:这里是剩余长度不能小于needle的长度for (int i = 0; i + needle.length() <= haystack.length(); i++) {boolean flag = false;for (int j = 0; j < needle.length(); j++) {if (haystack.charAt(i + j) != needle.charAt(j)) {flag = false;break;}flag = true;}if (flag) {return i;}}return -1;}
}

时间复杂度: O(n×m),其中n是字符串haystack的长度,m是字符串needle的长度。最坏情况下我们需要将字符串needle与字符串haystack的所有长度为m的子串均匹配一次。
空间复杂度: O(1)。我们只需要常数的空间保存若干变量。

Knuth-Morris-Pratt 算法: Knuth-Morris-Pratt算法,简称KMP算法,由Donald KnuthJames H. MorrisVaughan Pratt三人于1977年联合发表。Knuth-Morris-Pratt算法的核心为前缀函数,记作π(i),其定义如下:对于长度为m的字符串s,其前缀函数π(i)(0≤i<m)表示s的子串s[0:i]的最长的相等的真前缀与真后缀的长度。特别地,如果不存在符合条件的前后缀,那么π(i)=0。其中真前缀与真后缀的定义为不等于自身的的前缀与后缀。

我们举个例子说明:字符串aabaaab的前缀函数值依次为0,1,0,1,2,2,3
【1】π(0)=0,因为a没有真前缀和真后缀,根据规定为0(可以发现对于任意字符串π(0)=0必定成立);
【2】π(1)=1,因为aa最长的一对相等的真前后缀为a,长度为1
【3】π(2)=0,因为aab没有对应真前缀和真后缀,根据规定为0
【4】π(3)=1,因为aaba最长的一对相等的真前后缀为a,长度为1
【5】π(4)=2,因为aabaa最长的一对相等的真前后缀为aa,长度为2
【6】π(5)=2,因为aabaaa最长的一对相等的真前后缀为aa,长度为2
【7】π(6)=3,因为aabaaab最长的一对相等的真前后缀为aab,长度为3
有了前缀函数,我们就可以快速地计算出模式串在主串中的每一次出现。

如何求解前缀函数

长度为m的字符串s的所有前缀函数的求解算法的总时间复杂度是严格O(m)的,且该求解算法是增量算法,即我们可以一边读入字符串,一边求解当前读入位的前缀函数。

为了叙述方便,我们接下来将说明几个前缀函数的性质:
【1】π(i)≤π(i−1)+1:依据π(i)定义得:s[0:π(i)−1]=s[i−π(i)+1:i]。将两区间的右端点同时左移,可得:s[0:π(i)−2]=s[i−π(i)+1:i−1]。依据π(i−1)定义得:π(i−1)≥π(i)−1,即π(i)≤π(i−1)+1
【2】如果s[i]=s[π(i−1)],那么π(i)=π(i−1)+1。依据π(i−1)定义得:s[0:π(i−1)−1]=s[i−π(i−1):i−1]。因为s[π(i−1)]=s[i],可得s[0:π(i−1)]=s[i−π(i−1):i]。依据π(i)定义得:π(i)≥π(i−1)+1,结合第一个性质可得π(i)=π(i−1)+1

这样我们可以依据这两个性质提出求解π(i)的方案:找到最大的j,满足 s[0:j−1]=s[i−j:i−1],且s[i]=s[j](这样就有s[0:j]=s[i−j:i],即π(i)=j+1

注意这里提出了两个要求:
【1】j要求尽可能大,且满足s[0:j−1]=s[i−j:i−1]
【2】j要求满足s[i]=s[j]

π(i−1)定义可知:s[0:π(i−1)−1]=s[i−π(i−1):i−1](1)那么j=π(i−1)符合第一个要求。如果s[i]=s[π(i−1)],我们就可以确定π(i)。否则如果s[i]≠s[π(i−1)],那么π(i)≤π(i−1),因为j=π(i)−1,所以j < π(i−1),于是可以取(1)式两子串的长度为j的后缀,它们依然是相等的:s[π(i−1)−j:π(i−1)−1]=s[i−j:i−1]

当 s[i]≠s[π(i−1)]s[i]\neq s[\pi(i-1)]s[i]=s[π(i−1)] 时,我们可以修改我们的方案为:找到最大的 jjj,满足 s[0:j−1]=s[π(i−1)−j:π(i−1)−1]s[0:j-1]=s[\pi(i-1)-j:\pi(i-1)-1]s[0:j−1]=s[π(i−1)−j:π(i−1)−1],且 s[i]=s[π(i−1)]s[i]=s[\pi(i-1)]s[i]=s[π(i−1)](这样就有 s[0:j]=s[π(i−1)−j:π(i−1)]s[0:j]=s[\pi(i-1)-j:\pi(i-1)]s[0:j]=s[π(i−1)−j:π(i−1)],即 π(i)=π(i−1)+1\pi(i)=\pi(i-1)+1π(i)=π(i−1)+1)。

注意这里提出了两个要求:
【1】j要求尽可能大,且满足s[0:j−1]=s[π(i−1)−j:π(i−1)−1]
【2】j要求满足s[i]=s[j]

π(π(i−1)−1)定义可知j=π(π(i−1)−1)符合第一个要求。如果s[i]=s[π(π(i−1)−1)],我们就可以确定π(i)。此时,我们可以发现j的取值总是被描述为π(π(π(…)−1)−1)的结构(初始为π(i−1))。于是我们可以描述我们的算法:设定π(i)=j+1j的初始值为π(i−1)。我们只需要不断迭代j(令j变为π(j−1))直到s[i]=s[j]j=0即可,如果最终匹配成功(找到了j使得s[i]=s[j]),那么π(i)=j+1,否则π(i)=0

复杂度证明

时间复杂度部分,注意到π(i)≤π(i−1)+1,即每次当前位的前缀函数至多比前一位增加一,每当我们迭代一次,当前位的前缀函数的最大值都会减少。可以发现前缀函数的总减少次数不会超过总增加次数,而总增加次数不会超过m次,因此总减少次数也不会超过m次,即总迭代次数不会超过m次。

空间复杂度部分,我们只用到了长度为m的数组保存前缀函数,以及使用了常数的空间保存了若干变量。

如何解决本题

记字符串haystack的长度为n,字符串needle的长度为m。我们记字符串str=needle+#+haystack,即将字符串needlehaystack进行拼接,并用不存在于两串中的特殊字符#将两串隔开,然后我们对字符串str求前缀函数。因为特殊字符#的存在,字符串strhaystack部分的前缀函数所对应的真前缀必定落在字符串needle部分,真后缀必定落在字符串haystack部分。当haystack部分的前缀函数值为m时,我们就找到了一次字符串needle在字符串haystack中的出现(因为此时真前缀恰为字符串needle)。

实现时,我们可以进行一定的优化,包括:
1、我们无需显式地创建字符串str。为了节约空间,我们只需要顺次遍历字符串needle、特殊字符#和字符串haystack即可。
2、也无需显式地保存所有前缀函数的结果,而只需要保存字符串needle部分的前缀函数即可。特殊字符#的前缀函数必定为0,且易知π(i)≤m(真前缀不可能包含特殊字符#)。这样我们计算π(i)时,j=π(π(π(…)−1)−1)的所有的取值中仅有π(i−1)的下标可能大于等于m。我们只需要保存前一个位置的前缀函数,其它的j的取值将全部为字符串needle部分的前缀函数。
3、我们也无需特别处理特殊字符#,只需要注意处理字符串haystack的第一个位置对应的前缀函数时,直接设定j的初值为0即可。

这样我们可以将代码实现分为两部分:
1、第一部分是求needle部分的前缀函数,我们需要保留这部分的前缀函数值。
2、第二部分是求haystack部分的前缀函数,我们无需保留这部分的前缀函数值,只需要用一个变量记录上一个位置的前缀函数值即可。当某个位置的前缀函数值等于m时,说明我们就找到了一次字符串needle在字符串haystack中的出现(因为此时真前缀恰为字符串needle,真后缀为以当前位置为结束位置的字符串haystack的子串),我们计算出起始位置,将其返回即可。

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

时间复杂度: O(n+m),其中n是字符串haystack的长度,m是字符串needle的长度。我们至多需要遍历两字符串一次。
空间复杂度: O(m),其中m是字符串needle的长度。我们只需要保存字符串needle的前缀函数。

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

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

相关文章

信息检索策略和技巧

指定检索策略并检索 确定检索词 检索课题&#xff1a;查找与“新型冠状病毒疫苗研制进展”有关的学术论文 检索式(2019-nCoV or 2019新型冠状病毒 or nCov-2019 or SARS-CoV-2 or COVID-19) and (疫苗 or 预防针 or 防疫针 or vaccin or vaccine) 扩展检索词的方式 同义词…

Spring 拾枝杂谈—Spring原生容器结构剖析(通俗易懂)

目录 一、前言 二、Spring快速入门 1.简介 : 2. 入门实例 : 三、Spring容器结构分析 1.bean配置信息的存储 : 2.bean对象的存储 : 3.bean-id的快捷访问 : 四、总结 一、前言 开门见山&#xff0c;11.25日开始我们正式进入Java框架—Spring的学习&#xff0c;此前&…

Halcon [fill_up_shape],[close_circle],[dilation_circle]和[shape_trans]图像处理时填充区别

文章目录 文章专栏前言两者的区别fill_up_shapeshape_transclose_circledilation_circle 总结 文章专栏 我的Halcon开发 CSDN专栏 前言 本文用的案例是&#xff1a;Example: %HALCONEXAMPLES%/hdevelop/Applications/Completeness-Check/ball.hdev 两者的区别 [shape_trans]是…

PTA NeuDS-数据库题目集

一.判断题 1.在数据库中产生数据不一致的根本原因是冗余。T 解析&#xff1a;数据冗余是数据库中产生数据不一致的根本原因&#xff0c;因为当同一数据存储在多个位置时&#xff0c;如果其中一个位置的数据被修改&#xff0c;其他位置的数据就不一致了。因此&#xff0c;在数据…

LeetCode.203移除链表元素(原链表操作、虚拟头结点)

LeetCode.203移除链表元素 1.问题描述2.解题思路3.代码 1.问题描述 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val …

图书馆管理系统源码(Java)

Book包->内含Book类与BookList类 Book类 用于初始化图书并对其进行操作 BookList类 用于存放多本图书 Book类 package Book;public class Book {private String name;private String author;private int price;private String type;private boolean isBorrow;//写一个…

音频——S/PDIF

文章目录 BMC 编码字帧(sub-frame)格式帧(frame)格式参考S/PDIF 是 SONY 和 Philips 公司共同规定的数字信号传输规范,其实就是在 AES/EBU 上进行改动的家用版本。IEC60958 的标准规范囊括了以上两个规范。spdif 采用了双相符号编码(BMC),是将时钟信号和数据信号混合在一起…

强化学习,快速入门与基于python实现一个简单例子(可直接运行)

文章目录 一、什么是“强化学习”二、强化学习包括的组成部分二、Q-Learning算法三、迷宫-强化学习-Q-Learning算法的实现全部代码&#xff08;复制可用&#xff09;可用状态空间检查是否超出边界epsilon 的含义更新方程 总结 一、什么是“强化学习” 本文要记录的大概内容&am…

力扣:180. 连续出现的数字(Python3)

题目&#xff1a; 表&#xff1a;Logs ---------------------- | Column Name | Type | ---------------------- | id | int | | num | varchar | ---------------------- 在 SQL 中&#xff0c;id 是该表的主键。 id 是一个自增列。 找出所有至少连续…

Java 网络编程

文章目录 UDP Socket APIDatagramSocketDatagramPacket例子&#xff1a;UDP版本的回显服务器-客户端 TCP Socket APIServerSocketSocket例子&#xff1a;TCP版本的回显服务器-客户端 UDP Socket API DatagramSocket 这是一个 socket 类&#xff0c;本质上相当于一个文件&…

C3 多媒体查询

文章目录 前言CSS3 多媒体查询CSS2 多媒体类型CSS3 多媒体查询浏览器支持多媒体查询语法CSS3 多媒体类型多媒体查询简单实例 媒体类型媒体功能更多实例后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;CSS &#x1f431;‍&#x1f453;博…

SpringBoot 2 系列停止维护,Java8 党何去何从?

SpringBoot 2.x 版本正式停止更新维护&#xff0c;官方将不再提供对 JDK8 版本的支持 SpringBoot Logo 版本的新特性 3.2 版本正式发布&#xff0c;亮点包括&#xff1a; 支持 JDK17、JDK21 版本 对虚拟线程的完整支持 JVM Checkpoint Restore&#xff08;Project CRaC&…

Django之Auth模块

Auth模块引入 我们在创建一个Django项目之后&#xff0c;直接执行数据库迁移命令会自动生成很多表 django_sessionauth_userDjango在启动之后就可以直接访问admin路由&#xff0c;需要输入用户名和密码&#xff0c;数据参考的就是auth_user表,并且必须是管理员用户才能进入 【…

quickapp_快应用_某些css样式不兼容问题

样式问题 引入css样式文件[1] 单位px [2]选择器[3]盒模型[4]样式布局-默认弹性布局且不可取消[5-1]样式切换-类名的动态切换-语法[5-2]样式切换 - 类名的动态切换-目标元素[5-3] 样式切换 - 行内样式动态切换[6]background[7]overflow[8]border-radius[9]盒子阴影[10] 定位erro…

【调度算法】开放车间调度问题遗传算法(failed)

省流&#xff1a;这是一个错误的代码备份&#xff0c;如果你需要可以直接运行的完整代码&#xff0c;请移步GitHub。 本以为手搓了一个单机调度和并行机调度的遗传算法&#xff0c;就可以尝试写开放车间的遗传算法了&#xff0c;结果手搓了两天&#xff0c;开始作业时间和结束…

1、Mysql架构与历史

Mysql逻辑架构 最上层是服务并不是Mysql所独有的&#xff0c;大多数基于网络的客户端/服务器的工具或者服务都有类似的架构&#xff0c;比如连接处理&#xff0c;授权认证&#xff0c;安全等。 第二层是Mysql比较有意思的部分。大多数Mysql的核心服务都在这一层&#xff0c;…

torch.view()和.reshape()

view只能作用在连续的张量上&#xff08;张量中元素的内存地址是连续的&#xff09;。而reshape连续or非连续都可以。 调用x.reshape的时候&#xff0c;如果x在内存中是连续的&#xff0c;那么x.reshape会返回一个view&#xff08;原地修改&#xff0c;此时内存地址不变…

用户与组管理:如何在服务器系统中管理用户和权限

你是否想过&#xff0c;当你登录到一个服务器系统时&#xff0c;你是如何被识别和授权的&#xff1f;你是否知道&#xff0c;你可以通过创建和管理用户和组来简化和优化你的系统管理工作&#xff1f;你是否想了解一些常用的用户和组管理命令和技巧&#xff1f;如果你的答案是肯…

c语言实现简单的string

文章目录 前言一、注意事项二、代码valgrind扫描总结 前言 在c语言中利用面向对象的编程方式&#xff0c;实现类似c中的string类。 一、注意事项 所有与string结构体相关的函数全都没有返回值。 在c中&#xff0c;当产生临时对象时编译器会自动的加入析构函数&#xff0c;销毁…

Walrus 入门教程:如何创建模板以沉淀可复用的团队最佳实践

模板是 Walrus 的核心功能之一&#xff0c;模板创建完成后用户可以重复使用&#xff0c;并在使用过程中逐渐沉淀研发和运维团队的最佳实践&#xff0c;进一步简化服务及资源的部署。用户可以使用 HCL 语言自定义创建模板&#xff0c;也可以一键复用 Terraform 社区中上万个成熟…