力扣题目: 28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

关于这道题目最容易想到的是暴力解法,时间复杂度是O((m-n)*n)  , 但是KMP算法能达到O(m+n)

而对于 KMP算法,我想在这分享一下理解的思路(参考大佬的博文 : 图解KMP算法,带你彻底吃透KMP-CSDN博客)。

1.如果只是想使用KMP算法,不想了解原理,大概的使用思路是: 

(1)先得到needle字符串的next数组 。

        next数组是什么呢?在这之前需要普及下前缀后缀两个概念:前缀是指一个不含总字符串(eg : "sdfwffe")最后一个字符(eg: "e")但必须以第一个字符开头的子字符串(很多个);同理,后缀是不含第一个字符串但必须以最后一个字符结束的子字符串。

由此,可以给next数组下定义是: 总字符串的所有子字符串的最大相等前缀和后缀的长度所组成的表,如下例子所示。

图1

(2)得到next数组x后,开始采用双指针(主字符串设为S ,它的指针设为i , 目标字符串设为T,它的指针设为j), 让T和S先按照朴素解决法进行匹配,如图2所示,当指针i=j为某位置不匹配(i,j位置的字符不相同)时,指针i位置不移动,通过指针j的值锁定next表中的next值,然后将指针移动到next位置上(即 j=next),循环往复,得到结果。

图2

2.如果想深入了解KMP算法的基础原理,考虑从朴素法的过程进行分析,逐步分解这个过程。

观察图2,第一步第二步第三步,你会发现T的首字符是a,T的第二第三字符都不是a, 但是第一步匹配时已经证明了S的第二字符第三字符和T的相同,即不可能是a , 所以朴素法的第二步第三步是浪费的,因此证明了指针i不应该在第二轮循环时回到S的首位再前进一格(即i=1),应该直接到i=3 ; 对于目标字符串T的指针j,朴素法中都是在第二轮回到首位(j=0), 但是仔细查看,题目中的例子T,你会发现在第一步中指针已经移动到了j=5 , 在这之前的字符串是 abcab,查看next表可知最大相等前缀与后缀都是 ab , 此时如果还按照朴素法,使得j=0 , 那么在第二轮对比的两个字符是(S(i=3) 和T(j=0)),但是根据next表的定义可以知道,前缀ab与后缀ab在第一步已经证明了是相同的, 同时前缀也已经证明了和T的前(next=2)的相同,那么前缀(ab)=后缀(ab)=T的前(next=2)个字符即第二步执行的前两个字符判断已经判定相同了,所以执行红色字体代表的朴素法又会浪费时间,因此,我们直接把S的指针i和T的指针j跳过后缀ab,此时发现指针i=5(第一步执行结束后i的位置,因此,指针i其实一直向前,不用考虑回溯即可) , 指针j=0+2=2 即j=next=2 。 总结下来算法编写思路如上死记硬背算法解析所示,即i一直向前即可,不考虑回溯,j的话根据每一次匹配出的字符串得到next值,直接让j=next即可,举例过程如图3所示

图3

class Solution {
    // KMP 算法
    // ss: 原串(string)  pp: 匹配串(pattern)
    public int strStr(String ss, String pp) {
        if (pp.isEmpty()) return 0;
        
        // 分别读取原串和匹配串的长度
        int n = ss.length(), m = pp.length();
        // 原串和匹配串前面都加空格,使其下标从 1 开始
        ss = " " + ss;
        pp = " " + pp;

        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();

        // 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
        int[] next = new int[m + 1];
        // 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
        for (int i = 2, j = 0; i <= m; i++) {
            // 匹配不成功的话,j = next(j)
            while (j > 0 && p[i] != p[j + 1]) j = next[j];
            // 匹配成功的话,先让 j++
            if (p[i] == p[j + 1]) j++;
            // 更新 next[i],结束本次循环,i++
            next[i] = j;
        }

        // 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
        for (int i = 1, j = 0; i <= n; i++) {
            // 匹配不成功 j = next(j)
            while (j > 0 && s[i] != p[j + 1]) j = next[j];
            // 匹配成功的话,先让 j++,结束本次循环后 i++
            if (s[i] == p[j + 1]) j++;
            // 整一段匹配成功,直接返回下标
            if (j == m) return i - m;
        }

        return -1;
    }
}

作者:宫水三叶
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solutions/575568/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Logo

一站式 AI 云服务平台

更多推荐