leetcode刷题记录(二十八)——(KMP算法)28. 找出字符串中第一个匹配项的下标

(一)问题描述

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/给你两个字符串 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算法的题目。我花了好长时间才看懂,主要卡在了求next数组时怎么回退、为什么要这么回退,这里代码随想录讲得不是很清楚。 

        KMP算法主要用于解决字符串匹配的问题。这里我们将作为“比较标准”的needle字符串称为模板串,待比较的haystack字符串称为“文本串”。KMP能够在模板串中出现重复序列的情况下大幅节省比较时间,其基本思路在于将重复的部分记录下来,避免每次都从头比较。对于本文的问题,暴力算法非常好写,它的时间复杂度是O(n^2),使用KMP算法可以将时间复杂度降低到O(n)。 

leetcode刷题记录(二十八)——(KMP算法)28. 找出字符串中第一个匹配项的下标_第1张图片

      思路如上图所示,模板串为aacaaf,文本串为aacaabaafa。第一轮比较到模板串的f、文本串的b时出现了不匹配。此时通过比较已知,在不匹配的字母b和f之前,文本串中具有和模板串相同的aa,同时aa在两个字符串的开头也出现过。那么下次比较时,就不用再从模板串的头开始了(已经知道开头的aa匹配了),直接从两个字符串的aa的下一个元素开始比较。

       想要实现这个思路,就要记录重复序列的末尾位置在哪里,这样在遇到不匹配的字母时才知道下次比较要跳转到哪里。这里就要用到前缀表。所谓前缀就是包含首字母,但是不包含尾字母的所有子串;后缀就是包含尾字母,但是不包含首字母的所有子串

leetcode刷题记录(二十八)——(KMP算法)28. 找出字符串中第一个匹配项的下标_第2张图片

       我们这里要求的前缀表就是记录每个子串的最长相等前后缀长度。例如对于子串aacaa,它的最长相等相等前后缀的长度就是2(最长相等前后缀分别是aa和aa)。当遇到不匹配的情况时,直接跳转到当前元素之前的子串的最长相等前后缀的前缀末尾。这样说太绕了,看下面的例子。 

leetcode刷题记录(二十八)——(KMP算法)28. 找出字符串中第一个匹配项的下标_第3张图片

       这个前缀表在各个题解里通常定义为next数组。那么next数组怎么求呢,这是我个人觉得整个算法最不好理解的一个部分。 大致可以拆解为这三步:

  • 定义两个整数i和j,i用来遍历字符串,j用来记录最长前后缀的长度,整个过程有点像快慢指针。起始时j=0,i=1。
  • 每当遇见needle[i]==needle[j]的情况,j就往前移动一个。这很好理解,j相当于是前缀的末尾,i相当于是当前子串的末尾,也是后缀的末尾,出现相等,那自然说明当前i和j对应的元素属于相等前后缀的一部分。
  • 如果遇见needle[i]!=needle[j]的情况,那么当前元素就不属于最长相等前后缀,也就是说它的最长前后缀没有j+1这么长,有可能要比j+1短,也可能压根没有。那么j就要往回找,要回退。回退到哪里呢?要一直回退到j的上一个最长相等前后缀的位置,即next[j-1],直到needle[j]和当前needle[i]相等。如果没有这样的needle[j],就回退到0,即当前子串的最长相等前后缀长度为0

       我当时理解不了的就是不相等的情况:回退到和needle[i]相等的位置,怎么保证needle[i]和回退后的needle[j]之前的元素相等呢?这里需要明白:回退不是单纯找和needle[i]相等的元素,而是找上一个最长相等前后缀的末尾,要求这个末尾的值和needle[i]相等

leetcode刷题记录(二十八)——(KMP算法)28. 找出字符串中第一个匹配项的下标_第4张图片

class Solution {
    public int strStr(String haystack, String needle) {
        int[] next=findNext(needle);
        int j=0;
        for(int i=0;i0&&needle.charAt(j)!=haystack.charAt(i)){
                j=next[j-1];
            }
            if(needle.charAt(j)==haystack.charAt(i)){
                j++;
            }
            if(j==needle.length()){
                return i-needle.length()+1;
            }
        }
        return -1;
    }
    public int[] findNext(String needle){
        int[] next=new int[needle.length()];
        int j=0;
        next[0]=0;
        for(int i=1;i0&&needle.charAt(i)!=needle.charAt(j)){
                j=next[j-1];
            }
            if(needle.charAt(i)==needle.charAt(j)){
                j++;
            }
            next[i]=j;
        }
        return next;
    }
}

你可能感兴趣的:(leetcode刷题记录,leetcode,算法,职场和发展,java,数据结构)