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

leetcode.cn/problems/fi...

programmercarl.com/0028.%E5%AE...

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

示例 1:

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

示例 2:

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

提示:

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

KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

时间复杂度分析

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。

如何计算前缀表

接下来就要说一说怎么计算前缀表。

如图:

长度为前1个字符的子串a,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)

长度为前2个字符的子串aa,最长相同前后缀的长度为1。

长度为前3个字符的子串aab,最长相同前后缀的长度为0。

以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。

那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:

可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:

找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。

为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。

所以要看前一位的 前缀表的数值。

前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。

最后就在文本串中找到了和模式串匹配的子串了

构造next数组

我们定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。 代码如下:

kotlin 复制代码
fun getNext(next: Array<Int>, s: String) {
    var j = 0
    for (i in 1..<s.length) {
        while (j > 0 && s[i] != s[j]) {
            j = next[j - 1]
        }
        if (s[i] == s[j]) {
            j++
        }
        next[i] = j
    }
}

构造next数组其实就是计算模式串s,前缀表的过程。 主要有如下三步:

  1. 初始化
  2. 处理前后缀不相同的情况
  3. 处理前后缀相同的情况

最终代码如下:

kotlin 复制代码
class Solution {
    fun strStr(haystack: String, needle: String): Int {
        val length = needle.length
        val next = Array(size = length) { 0 }
        getNext(next, needle)

        var j = 0
        for (i in 0..<haystack.length) {
            while (j > 0 && needle[j] != haystack[i]) {
                j = next[j - 1]
            }
            if (needle[j] == haystack[i]) {
                j++
            }
            if (j == needle.length) {
                return i - needle.length + 1
            }
        }
        return -1
    }

    fun getNext(next: Array<Int>, s: String) {
        var j = 0
        for (i in 1..<s.length) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1]
            }
            if (s[i] == s[j]) {
                j++
            }
            next[i] = j
        }
    }
}
相关推荐
云析赢指标公式网47 小时前
文华WH6布林轨道均线强弱共振指标公式
前端·算法
luj_17687 小时前
一线一区一变破解人盯人防守密码
开发语言·网络·c++·经验分享·算法
Wang's Blog7 小时前
Java框架快速入门: Spring Security+OAuth2之字段验证与自定义验证注解实战
java·算法·spring
带多刺的玫瑰7 小时前
Leecode#29刷题之两数相除
java·python·算法
smj2302_796826527 小时前
解决leetcode第4017题数组中的峰值II
python·算法·leetcode
午彦琳8 小时前
2026.9.9
数据结构·算法·leetcode
luj_17688 小时前
中锋卡位与开放线博弈
c语言·开发语言·网络·经验分享·算法
洋不写bug8 小时前
二叉树(四)经典算法题目解析,公共祖先,二叉树构造,非递归遍历
数据库·算法·二叉树·二叉树构造·公共祖先
hansang_IR8 小时前
【题解】P4454 [CQOI2018] 破解D-H协议
c++·算法·密码学·数论
梓䈑8 小时前
【动态规划系列】路径问题、子数组 和 子序列问题、背包问题
c++·算法·动态规划