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

0 阅读3分钟

leetcode.cn/problems/fi…

programmercarl.com/0028.%E5%AE…

给你两个字符串 haystack 和 needle ,请你在 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 仅由小写英文字符组成

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

时间复杂度分析

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

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

如何计算前缀表

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

如图:

KMP精讲5

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

KMP精讲6

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

KMP精讲7

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

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

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

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

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

KMP精讲2

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

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

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

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

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

构造next数组

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

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. 处理前后缀相同的情况

最终代码如下:

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
        }
    }
}