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

发布时间 2023-03-24 20:14:23作者: ZDREAMER

题目描述:

给你两个字符串 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 经典题目。

 

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

 

什么是前缀表

写过KMP的同学,一定都写过next数组,那么这个next数组究竟是个啥呢?

next数组就是一个前缀表(prefix table)。

前缀表有什么作用呢?

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

为了清楚地了解前缀表的来历,我们来举一个例子:

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

请记住文本串和模式串的作用,对于理解下文很重要,要不然容易看懵。所以说三遍:

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

 

如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

前缀表是如何记录的呢?

首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

那么什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

 

 

前缀表与next数组

很多KMP算法的时间都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?

next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。

为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。

其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。

求前缀表就是求next数组!

 

 

使用next数组来匹配

以下我们以前缀表统一减一之后的next数组来做演示。

有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。

注意next数组是新前缀表(旧前缀表统一减一了)。

匹配过程动画如下:

 

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

  1. 初始化
  2. 处理前后缀不相同的情况
  3. 处理前后缀相同的情况
class Solution{
    public int strStr(String haystack,String needle){
        if(needle.length()==0){
            return 0;
        }
        int next[]=new int [needle.length()];
        getNext(next,needle);
        int j=0;
        for(int i=0;i<haystack.length();i++){//前缀表不减1,i从0开始
            while(j>0&&needle.charAt(j)!=haystack.charAt(i)){
                //处理前后缀不相同的情况
                j = next[j-1];
            }
            ////处理前后缀相同的情况
            if(needle.charAt(j)==haystack.charAt(i)){
                //// 匹配,j和i同时向后移动,i后移在for循环里
                j++;
            }
            if(j==needle.length()){//文本串s里出现了模式串t
                return i - needle.length()+1;
            }
        }
        return -1;
    }
    public void getNext(int next[],String s){
        //构造next数组
        //初始化
        int j=0;
        next[0] = 0;
        for(int i=1;i<s.length();i++){
            while(j>0&&s.charAt(j)!=s.charAt(i)){
                
                j = next[j-1];//向前回退
            }
            
            if(s.charAt(j)==s.charAt(i)){
                //找到相同的前后缀
                j++;
            }
            next[i]=j; // 将j(前缀的长度)赋给next[i]
        }
    }

}