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

一、题目

  给你两个字符串 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
  • haystackneedle 仅由小写英文字符组成

二、C# 题解

  题目直接调用库函数即可。这里复习一下 KMP 算法,首先构造 next 数组。注意,next 数组长度为 needle + 1。采取这样的做法是为了使后缀指针 j 不会指向 -1,这里 next[0] 的位置就充当了 next[-1] 的位置,以免数组越界报错。
  为什么 j 一定要指向 -1 呢,不可以指向 0 就停止回退吗?不行,因为 j 指向 0 时,无法区分 j 此时是回退到 0(此时 j 不应该 + 1,因为下一次还要指向第一个字符,即 j = 0),还是指前后缀的最后一位相等(此时 j 应该 + 1,下一次 j = 1)。
  正因 next 数组长度为 needle + 1,else j = next[j + 1] - 1; 该段代码需要对 j 先 + 1,取出 next 值后再 -1。完整代码如下:

public class Solution {
    
    
    public int StrStr(string haystack, string needle) {
    
    
        int[] next = GetNext(needle); // 获取 next 数组
        int i = 0, j = 0;             // i 指向 haystack,j 指向 needle
        while (i < haystack.Length) {
    
    
            // j 回退到 -1 或字符相同,则进行下一位匹配
            if (j == -1 || haystack[i] == needle[j]) {
    
     i++; j++; }
            else j = next[j + 1] - 1;             // 否则,j 依据 next 数组回退
            if (j == needle.Length) return i - j; // j 遍历完 needle,返回 needle 起始位置
        }
        return -1;
    }

    // 获取 next 数组
    public int[] GetNext(string needle) {
    
    
        int[] next = new int[needle.Length + 1]; // next 长度为 needle + 1,next[0] 空着,作用是避免 j 越界
        next[1] = 0;                             // next[1,n] 存储 needle 对应的字符下标
        int i = 1, j = 0;                        // i 指向 needle[0,i] 后缀末尾,j 指向 needle[0,i] 前缀末尾
        while (i < needle.Length) {
    
    
            // j 回退到 0 或后缀相同,则 i、j 一同前进,并给 next 赋值
            if (j == 0 || needle[i - 1] == needle[j - 1]) 
                next[++i] = ++j;
            else j = next[j]; // 否则,j 回退
        }
        return next;
    }
}
  • 时间复杂度: O ( m + n ) O(m+n) O(m+n),其中, m m mhaystack 长度,n 为 needle 长度。
  • 空间复杂度: O ( n ) O(n) O(n)

  《大话数据结构》中提到了 KMP 的优化,改动为 GetNext 函数中的如下代码,即加了一行判断处理。下面对改代码做出简略解释:

            // j 回退到 0 或后缀相同,则 i、j 一同前进,并给 next 赋值
            if (j == 0 || needle[i - 1] == needle[j - 1]) 
                if (needle[i] == needle[j]) next[++i] = next[++j];
                else next[++i] = ++j;
            else j = next[j]; // 否则,j 回退

  即判断前后缀后一个字符是否相同。如果相同,直接将之前的 next 结果复制过来;否则,按照之前的处理即可。下面举例说明这样做的正确性,图中,黑色箭头表示 i,红色箭头表示 j。

在这里插入图片描述
  可以看到,最后一位不相同,按照原本的规则,next[6] 应取 3(绿色箭头指向位置,从 1 开始计数)。但是由于前缀和后缀都是 ab,且其后一位均为 e。因此将 next[6] 赋值为 next[3],即 next[6] = next[3] = 1。
在这里插入图片描述
  为什么可以这样呢,因为 next[6] 是 e,已经不匹配了,那么跳到相同字符的 next[3] 处也一定不匹配,因此直接跳到 next[next[3]](蓝色箭头指向位置)就好啦!完整代码如下:

public class Solution {
    
    
    public int StrStr(string haystack, string needle) {
    
    
        int[] next = GetNext(needle); // 获取 next 数组
        int i = 0, j = 0;             // i 指向 haystack,j 指向 needle
        while (i < haystack.Length) {
    
    
            // j 回退到 -1 或字符相同,则进行下一位匹配
            if (j == -1 || haystack[i] == needle[j]) {
    
     i++; j++; }
            else j = next[j + 1] - 1;             // 否则,j 依据 next 数组回退
            if (j == needle.Length) return i - j; // j 遍历完 needle,返回 needle 起始位置
        }
        return -1;
    }

    // 获取 next 数组
    public int[] GetNext(string needle) {
    
    
        int[] next = new int[needle.Length + 1]; // next 长度为 needle + 1,next[0] 空着,作用是避免 j 越界
        next[1] = 0;                             // next[1,n] 存储 needle 对应的字符下标
        int i = 1, j = 0;                        // i 指向 needle[0,i] 后缀末尾,j 指向 needle[0,i] 前缀末尾
        while (i < needle.Length) {
    
    
            // j 回退到 0 或后缀相同,则 i、j 一同前进,并给 next 赋值
            if (j == 0 || needle[i - 1] == needle[j - 1]) {
    
    
                if (needle[i] == needle[j]) next[++i] = next[++j];
                else next[++i] = ++j;
            }
            else j = next[j]; // 否则,j 回退
        }
        return next;
    }
}
  • 时间复杂度: O ( m + n ) O(m+n) O(m+n)
  • 空间复杂度: O ( n ) O(n) O(n)

猜你喜欢

转载自blog.csdn.net/zheliku/article/details/132820444