LeetCode 刷题系列 28. 实现 strStr()

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  -1 。

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:

输入:haystack = "hello", needle = "ll"
输出:2
示例 2:

输入:haystack = "aaaaa", needle = "bba"
输出:-1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-strstr
 

思路:此题典型应用 kmp 算法,解决字符串匹配问题。

字符串匹配问题题型:假设有两个字符串 A 与 B ,且 A 长度大于 B 长度,问字符串 B 是否为A的一个子串。

kmp 算法的原理实际上是优化了暴力求解算法。

回顾一下暴力求解算法,两个for循环,分别从A 与B的第一个字符A[ i ] 与 B[ j ]开始,依次比较,直到某 i 次循环中,B完全与A 中字符串吻合,则可以判断B是A的子串。

显然,这样中间会有太多次的无效匹配,效率低下。

如果我们能够相关办法,省掉其中的无效循环,那么就可以大大的提高效率。

盗个图,设图中的长字符串为A 串,短的为 B 串,已经匹配上的子串为C串。

 则可以看出,子串 abaab 的头两个字符组成的子串,与最后两个字符组成的子串是相同的。

则我们可以直接把B串向右平移两个位置,如下图所示。

这就是KMP的核心思想,省略无效循环比较。

这里介绍几个概念:

前缀、后缀、相同前缀后缀的最大长度(为表述方便,下文均用公共最大长指代),为了直观一点,我们直接举例:

abcdef的前缀:a、ab、abc、abcd、abcde(注意:abcdef不是前缀)
abcdef的后缀:f、ef、def、cdef、bcdef(注意:abcdef不是后缀)
abcdef的公共最大长:0(因为其前缀与后缀没有相同的)
ababa的前缀:a、ab、aba、abab
ababa的后缀:a、ba、aba、baba
ababa的公共最大长:3(因为他们的公共前缀后缀中最长的为aba,长度3)

我们可以容易的看出C串的公共最大长为2,所以我们可以直接把B串平移C串的C的长度 -  最大公共最大长个 位置。

若是我们能够求出B串中以每个位置的字符结尾的子串的最大公共最大长,那么每次我们遇到不匹配的字符时,都可以直接平移 公共最大长 个位置,再继续匹配。

设数组 K 的长度为 B的长度,其中K[ i ]表示以B [ i ] 结尾的子串的最大公共长,求解K有点费劲。但是,细心的我们能够发现 K[ i ] - 1 值表示以B [ i ] 结尾的子串的最大前缀中最后一个元素的下标。

所以,我们可以求解数组next,其中next[ i ] 表示以B [ i ] 结尾的子串的最大前缀中最后一个元素的下标。如果某个以B [ j ] 结尾的最大公共最大长为 0 ,则next[ j ] = -1。

容易得到:next[ 0 ] = -1

假设我们已经得到了next[ 0 ] 到next[ i-1 ]的值,现在求解next[ i ]。

kmp算法详解参考:如何更好地理解和掌握 KMP 算法? - 知乎

class Solution {
            public int strStr(String haystack, String needle) {
        if(needle.length() ==0) {
            return 0;
        }

        if(haystack.length() == 0) {
            return -1;
        }

        int[] next = new int[needle.length()]; // 存放needle 中以当前字符结尾所匹配的最大前缀中相同字符的位置,若是没有默认为-1
        next[0] = -1; // 第一个字母无最大前缀,所以是-1

        for(int i=1;i<needle.length();i++) {
            int j = next[i-1];

            // 找到以当前字符needle[i] 结尾的最大前缀的下标,若是找不到,令next[i] = -1;若是找到,next[i]=最大前缀的长度-1
            while(true) {
                if(needle.charAt(j+1)!=needle.charAt(i) && j>=0) {
                    j = next[j];

                } else {
                    break;
                }
            }
            if(needle.charAt(j+1)==needle.charAt(i)){
                next[i] = j+1;
            } else {
                next[i] = -1;
            }
        }
        printArray(next);
        int i=0; // haystack 字符位置
        int j=0; // needle

        for(;i<haystack.length();i++) {
            while (j>0 && needle.charAt(j) != haystack.charAt(i)){ //从字符串 needle 的下标 j 开始往前,直到 needle[j] == haystack[i] 或者 j==0 为止
                j=next[j-1]+1; // 更新j的值为 以 needls[j] 的上一个字符needls[j-1]结尾的最大前缀结尾元素的下标 + 1
            }
            if(needle.charAt(j)==haystack.charAt(i)) {
                j++;
            } else {
                if(j>0) {
                    j = next[j-1]+1;
                }
            }
            if(j==needle.length()) {
                break;
            }

        }

        if(i<haystack.length()) {
            return i-needle.length() + 1;
        } else {
            return -1;
        }

    }
}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值