[leetcode] 28. 找出字符串中第一个匹配项的下标

文章目录

  • 题目描述
  • 解题方法
    • 方法一:双指针
      • java代码
      • 复杂度分析
    • 方法二:KMP算法
      • java代码
      • 复杂度分析

题目描述

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

示例 1:

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

示例 2:

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

提示:

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

解题方法

方法一:双指针

haystack的起始位置开始与needle的起始位置匹配,一旦发现不匹配的字符,则haystack从上次遍历的起始位置往后移动一格,needle重新回到起始位置进行下一次匹配。若haystack遍历到末尾之前匹配成功,则返回haystack匹配成功的起始下标;否则,返回-1

java代码

public int strStr(String haystack, String needle) {for (int i = 0; i <= haystack.length() - needle.length(); i++) {for (int j = 0; j < needle.length(); j++) {if (haystack.charAt(i + j) != needle.charAt(j)) {break;}if (j == needle.length() - 1) {return i;}}}return -1;
}

复杂度分析

时间复杂度:设haystack长度为nneedle长度为m,最坏的情况下haystack遍历的次数为n - m,每次遍历needle的匹配长度为m,则渐进时间复杂度 O ( m × ( n − m ) ) O(m \times (n-m)) O(m×(nm))
空间复杂度: O ( 1 ) O(1) O(1),除了双指针不需要存储其他变量。

方法二:KMP算法

我们从方法一可以看到,每次我们进行字符串匹配时,如果haystackneedle不匹配,则haystack从上一次遍历的起始位置往后移动一格,再与needle从头开始匹配。假设haystack上一次遍历从起始位置开始与needle的前k个字符匹配,那么有没有一种方法使我们不需要让haystack回到上一次起始位置的下一格与needle从头匹配,而是继续在起始位置后面的第k个坐标与needle进行后续的匹配呢?答案是可以,需要我们使用KMP算法

KMP算法的核心就是最长前缀和,那么什么是最长前缀和呢?

假设有一个字符串 a a b a a a b a aabaaaba aabaaaba,我们设 n e x t next next数组为每个位置的最长前缀和,我们需要求 n e x t [ i ] next[i] next[i] n e x t [ i ] next[i] next[i]代表的含义是匹配的字符串以 i i i位置为终点时(不包括终点 i i i),能与原字符串前缀匹配的最长前缀和,匹配的字符串不能与原字符串前缀是同一个字符串。

  • i = 0 i=0 i=0时,此时 i i i前面没有字符,故 n e x t [ 0 ] = 0 next[0]=0 next[0]=0
  • i = 1 i=1 i=1时,此时 i i i前面的字符为 a a a,与字符串起始位置的 a a a在同一个位置,由于匹配的字符串与原字符串前缀是同一个字符串,此时也记 n e x t [ 1 ] = 0 next[1]=0 next[1]=0
  • i = 2 i=2 i=2时,此时 i i i前面的字符为 a a a,后缀 a a a与原字符串前缀 a a a匹配,故 n e x t [ 2 ] = 1 next[2]=1 next[2]=1
  • i = 3 i=3 i=3时,此时 i i i前面的字符为 b b b,由于匹配的字符串结尾为 b b b,无法与原字符串前缀 a a a或者 a a aa aa匹配,所以 n e x t [ 3 ] = 0 next[3] = 0 next[3]=0
  • i = 4 i=4 i=4时, i i i前面的字符为 a a a,此时后缀 a a a只能匹配原字符串前缀 a a a,故 n e x t [ 4 ] = 1 next[4]=1 next[4]=1
  • i = 5 i=5 i=5时, i i i前面的字符为 a a a,此时后缀 a a aa aa可以匹配原字符串前缀 a a aa aa,故 n e x t [ 5 ] = 2 next[5]=2 next[5]=2
  • i = 6 i=6 i=6时, i i i前面的字符为 a a a,此时后缀 a a aa aa可以匹配原字符串前缀 a a aa aa,故 n e x t [ 6 ] = 2 next[6]=2 next[6]=2
  • i = 7 i=7 i=7时, i i i前面的字符为 b b b,此时后缀 a a b aab aab可以匹配原字符串前缀 a a b aab aab,故 n e x t [ 7 ] = 3 next[7]=3 next[7]=3

此时即求出了 n e x t next next数组每个位置的最长前缀和

求出 n e x t next next数组有什么用呢?那我们再举一个haystackneedle 字符串匹配的例子。

needle字符串还是 a a b a a a b a \color{red}aabaaaba aabaaabahaystack字符串为 a a b a a a b b \color{red}aabaaabb aabaaabb a a b a a a b a \color{blue}aabaaaba aabaaaba。当haystack从起始位置匹配到字符串 a a b a a a b b \color{red}aabaaabb aabaaabb时,此时haystack匹配字符串末尾的 b b bneedle末尾的 a a a不匹配。一般情况下,我们就将haystack移动到起始位置的第二个字符,与needle从头开始匹配了。但是有了next数组之后,不匹配的位置在needle下标 i = 7 i=7 i=7,我们检查到 n e x t [ 7 ] = 3 next[7] = 3 next[7]=3,也就是说haystack匹配的字符串 a a b a a a b b \color{red}aabaaabb aabaaabb中可以再从后缀 a a b b \color{red}aabb aabb开始与needle i = 3 i=3 i=3位置的字符开始匹配,此时i=3位置的字符为 a a a与后缀字符 b b b不匹配,next[3]=0,此时没有后缀与needle前缀匹配了,此时haystack再从 a a b b aabb aabb最后一个后缀 b b b开始,与needle从头进行匹配。可以看出在匹配的过程中,只要haystack的匹配位置移动到了第k个字符,则haystack就不需要再回到第k个字符之前从头遍历,只需要移动needle的匹配位置比较haystack的第k个字符,这样大大减少了匹配时间。

java代码

public int strStr(String haystack, String needle) {if (haystack == null || needle == null ||haystack.length() < needle.length()) {return -1;}if (needle.length() == 0) {return 0;}char[] str1 = haystack.toCharArray();char[] str2 = needle.toCharArray();int[] next = getNextArr(str2);int i1 = 0;int i2 = 0;while (i1 < str1.length && i2 < str2.length) {if (str1[i1] == str2[i2]) {i1++;i2++;} else if (i2 > 0) {// haystack与needle在needle第i2位置的字符不匹配之时,先让i2回到next[i2]// 此时needle从0 ~ i2-1的前缀与haystack从i1-i2 ~ i-1的字符串匹配i2 = next[i2];} else {i1++;}}return i2 == str2.length ? i1 - i2 : -1;
}// 使用kmp算法计算next最长前缀和数组
public int[] getNextArr(char[] str) {if (str.length == 1) {return new int[]{0};}// next[i]代表以i位置为终点时(不包括i),最长后缀与最长前缀匹配的长度。后缀的起点位置不能从下标0开始。int[] next = new int[str.length];// next下标0和1之前都没有后缀与前缀匹配next[0] = 0;next[1] = 0;int i = 2;// 最长前缀和计数int cnt = 0;while (i < next.length) {if (str[i - 1] == str[cnt]) {next[i++] = ++cnt;} else if (cnt > 0) {// 当前后缀最后的字符不匹配之时,先让后缀的起始位置移动到更靠后的位置,与next[cnt]处的字符进行比较//(此时str的后缀与cnt之前的字符串匹配)cnt = next[cnt];} else {next[i++] = 0;}}return next;
}

复杂度分析

时间复杂度:设haystack长度为nneedle长度为mhaystack只会遍历一次,needle也会遍历一次,时间复杂度 O ( m + n ) O(m + n) O(m+n)
空间复杂度: O ( m ) O(m) O(m),需要留出 n e x t next next最长前缀和数组的空间。


  • 个人公众号
    个人公众号
  • 个人小游戏
    个人小游戏

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

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

相关文章

数据结构_找环,破环题-2.5

一. 判断单链表有无环 a. 错误的思路&#xff1a;遍历陷入死循环 1&#xff09;和相交的遍历思路一样&#xff0c;找指向相同。 错误点 一直在死循环。 思考点&#xff1a;如何破环 b. 个人思路&#xff1a;反转链表回首结点 1&#xff09;目前的经验&#xff0c;无非就…

STM32F1 - 点灯-寄存器模式

点灯 实验概述&#xff1a;Step1> 建立工程Step2> 宏定义 - 寄存器地址 实验概述&#xff1a; 用配置寄存器的方式&#xff0c;开关一个LED灯&#xff0c; 只用标准库中提供的启动文件&#xff0c; Step1> 建立工程 出现错误&#xff1a;导入文件类型错误 keil5编译中…

ELAdmin后台启动

版本选择 ELAdmin官网地址&#xff1a;https://eladmin.vip/ 有 JPA 和 MyBatis两个版本&#xff0c;之前只有 JPA&#xff0c;考虑到国内复杂的业务情况增加了 MyBatis 版本。我最终也选择了使用 MyBatis版本。 代码 仓库地址&#xff1a;https://gitee.com/elunez/eladmin…

【日志记录】——单片机可执行文件合并

一&#xff1a;需求场景 现在有一片单片机&#xff0c;执行程序包括自定义boot和应用程序app, 在将打包好的固件给到生产时有以下问题&#xff0c;由于要通过jlink烧录boot&#xff0c;然后上电启动boot&#xff0c;通过boot烧录初始化程序&#xff0c;过程过于复杂&#xff0…

物联网自动虫情测报仪器

TH-CQ3S在农业生产的进程中&#xff0c;病虫害的防治始终是关键的一环。然而&#xff0c;传统的病虫害监测手段往往存在着效率低下、准确度不高等问题&#xff0c;这无疑给农业生产带来了巨大的困扰。好在&#xff0c;随着科技的飞速发展&#xff0c;一款基于物联网技术的自动虫…

【问题篇】activiti工作流转办并处理备注问题

当处理activiti转办问题时&#xff0c;需要做的就是处理审批人和备注问题。 处理的思路是&#xff0c;先将当前环节标志成转办标签&#xff0c;再通过BUSINESS_KEY_找到流程实例的历史记录&#xff0c;找到最新的一条复制一份出来&#xff0c;表示需要转办到的人的历史记录并设…

RecombiMAb anti-mouse Ly6G,1A8-CP129单克隆抗体

1A8-CP129单克隆抗体是原始1A8单克隆抗体的重组嵌合型抗体。可变结构域序列与原始1A8相同&#xff0c;但是恒定区序列已经从大鼠IgG2a变为小鼠IgG2a。1A8-CP129单克隆抗体像原始克隆号的大鼠IgG2a抗体一样&#xff0c;不包含Fc突变。 1A8-CP129单克隆抗体与小鼠Ly6G反应。Ly6G分…

C# CAD交互界面-自定义工具栏(二)

运行环境 vs2022 c# cad2016 调试成功 一、引用 acdbmgd.dllacmgd.dllaccoremgd.dllAutodesk.AutoCAD.Interop.Common.dllAutodesk.AutoCAD.Interop.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.T…

2. Maven 继承与聚合

目录 2. 2.1 继承 2.2继承关系 2.2.1 思路分析 2.2.2 实现 2.1.2 版本锁定 2.1.2.1 场景 2.1.2.2 介绍 2.1.2.3 实现 2.1.2.4 属性配置 2.2 聚合 2.2.1 介绍 2.2.2 实现 2.3 继承与聚合对比 maven1&#xff1a;分模块设计开发 2. 在项目分模块开发之后啊&#x…

基于Vue的移动端UI框架整理

一、Vant 官方地址&#xff1a;https://youzan.github.io/vant/#/zh-CN/ 简介&#xff1a;有赞公司开发。 特性&#xff1a;60 高质量组件、90% 单元测试覆盖率、完善的中英文文档和示例、支持按需引入、支持主题定制、支持国际化、支持 TS、支持 SSR。 特别说明&#xff1…

Java老兵 转C语言,需要学习的点(最易懂的解释)

一、static 1.1 修饰函数内的局部变量&#xff1a; void sayHi(void) { static int index 5;index; }多次调用sayHi函数&#xff0c;index 5 只有在第一次调用的时候初始化一次&#xff0c;后面的多次调用&#xff0c;此句话就不执行了。 1.2 修饰全局变量或…

python将Word页面纸张方向设置为横向

通过python-docx的章节属性&#xff0c;就可以更改纸张方向、纸张尺寸。 import docx from docx.enum.section import WD_ORIENT from docx.shared import Cmdocument docx.Document() section document.sections[0]# 设置纸张大小为A4大小 section.page_width Cm(21) sect…