[字符串]实现 strStr()

166 阅读5分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述

原文链接:28. 实现 strStr()
具体描述: 实现 strStr() 函数。

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

说明: s 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:

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

示例 2:

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

示例 3:

输入:haystack = "", needle = "" 输出:0

提示:

  • 0 <= haystack.length, needle.length <= 5 * 10^4
  • haystackneedle 仅由小写英文字符组成

注意:这道题目是判断是否存在子串,并返回起始位置,不是子串的第一个字符在父串首次出现的位置!(尴尬的一比,我刚刚以为就是这样,以为又碰见水题啦!)


二、思路分析

我们统一把haystack称呼为文本串needle称呼为模式串,题目要求文本串中是否含有模式串,含有的话就返回第一个起始位置!

第一种思路:水题的方式,遍历文本串,依次截取模式串长度,判断是否与模式串相同,相同返回位置就可以了!


第二种思路:大名鼎鼎的KMP算法,专门为判断是否包含字串而出生的算法!(非常的白话文!) 大家做好心里准备,第一次听说这个可能会有点晦涩难懂!没关系,第一次做有个大概,以后就慢慢熟悉了!

第一:什么是KMP算法? KMP算法就是为了求是否包含模式串的算法! 如果暴力破解的话,就是两个for循环,第一层就是遍历文本串,定义文本串起始下标,第二层遍历模式串,从文本串的起始下标开始比较,如果相等,继续比较,如果两个字符不相等的时候,下次循环比较还是从模式串的起始位置开始!而KMP不是从起始位置开始,而是通过某种方式判断出那个位置开始!不需要做一些重复的比较!

举个例子: 文本串:aabaabaaf 模式串:aabaaf

第一次遇到不相等的时候,也就是b和f,暴力破解的话就是文本串从第二个a开始和模式串第一个a开始重新比较 KMP算法的话就是,我根据某种规则,我第一层已经走到了aabaab啦,我不从第二个位置开始,我还保持我的位置!第二层我也不从头开始,直接从aab的b开始往后比较,比较到最后发现一样,结果就出来啦!

简单说就是KMP可以让我们不从头开始比较!


第二:如果理解最长前后缀长度? 那为什么我们不需要从头比较那?这里涉及到了最长前后缀概念!

我们先理解什么叫前缀? 前缀就是包含首个字符,但是不包含末尾字符,构成的组合都可以叫做前缀!

aabaaf,最后一个a的前缀有那些?(一共五个,注意看列的对应) a aa aab aaba aabaa

什么叫后缀? 后缀就是不包含首个字符,但是包含末尾字符,构成的组合都可以叫做后缀!

aabaaf,最后一个a的后有那些?(一共五个,注意看列的对应) f af aaf baaf abaaf

那知道了前缀和后缀,什么叫某个字符的最长前后缀长度那? 就是不包含当前字符的前缀和后缀的相等的最大长度!

aaba的最长前后缀是什么? 根据上面我们知道了他的前缀是那些,后缀是那些,相等的只有aa,所以说最长前后缀的长度是2!

那这个最长前后缀长度有啥子用嘛! 我们回到最开始的解释,KMP不从头开始,而是从b开始比较,我们知道模式串f位置的最长前后缀的长度是2,也是b下标的位置,所以可以暂且认为这意味其实位置是最长前后缀长度的位置!

那为什么起始位置正好是最长前后缀长度的位置?

文本串:aabaabaaf 模式串:aabaaf

因为是比较到f的时候,是第一次出现不相等,则意味这前面都相等对吧! 从模式串的b的位置开始看,前面是aa,f的前面也是aa,两个正好相等;所以可以从b开始,也正好是最长前后缀的长度的位置!good!!!!


第三:如何求next数组? 既然理解了最长前后缀长度的位置正好是重新比较的位置,那问题来了,怎么代码求出最长前后缀长度那? 这里我们先统一一个名字,next数组用来存储模式串每个字符对应的最长前后缀长度

第一步:初始化

// 第一步:初始化数据
int j = 0; // j 表示 前缀末尾下标,还表示最长前后缀长度
next[j] = 0;
int i = 1;// i 表示 后缀末尾下标

此时需要遍历模式串的for循环,来个next数组依次赋值!

for (i = 1; i < s.length(); i++){}

下面都是循环体的内容!

第二步:前缀末尾字符不等于后缀末尾字符

// 第二步:如果前缀末尾不等于后缀末尾,注意是while循环!
while (j > 0 && s.charAt(i) != s.charAt(j)){
    j = next[j - 1];// j回到上一个j的位置
}

第三步:前缀末尾字符等于后缀末尾字符

// 第三步:前缀末尾等于后缀末尾
if (s.charAt(i) == s.charAt(j)){// 注意是if,因为i,j代表的就是末尾,末尾相等啦,相等的前后缀长度+1,j++
    j++;    
}

第四步:next数组复制

// 第四步:next[i] 赋值,j代表最长前后缀长度!
next[i] = j;

三、AC代码

水题方式:

class Solution {
    public int strStr(String haystack, String needle) {
        StringBuilder sb = new StringBuilder();
        if ("".equals(needle) == true) return 0;
        for (int i = 0; i < haystack.length(); i++){
            if (haystack.length() - i < needle.length()) return -1;
        
            sb = new StringBuilder(haystack.substring(i, needle.length() + i));
            if (needle.equals(sb.toString()) == true) return i;         
        }
        return -1;
    }
}

kmp方式:

class Solution {
    public void getNext(int[] next, String s){
        // 第一步:初始化数据
        int j = 0; // j 表示 前缀末尾下标,还表示最长公共前后缀长度
        next[j] = 0;

        if (s.length() == 1){
            return;
        }

        int i = 1;// i 表示 后缀末尾下标

        for (i = 1; i < s.length(); i++){
            // 第二步:如果前缀末尾不等于后缀末尾,注意是while循环!
            while (j > 0 && s.charAt(i) != s.charAt(j)){
                j = next[j - 1];// j回到上一个j的位置
            }

            // 第三步:前缀末尾等于后缀末尾
            if (s.charAt(i) == s.charAt(j)){// 注意是if,因为i,j代表的就是末尾,末尾相等啦,j++
                j++;    
            }
            // 第四步:next[i] 赋值
            next[i] = j;
        }
    }
    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++){
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)){
                j = next[j - 1];
            }
            if (haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            if (j == needle.length()){
                return i - needle.length() + 1;
            }
        }
        return -1;
    }
}

四、总结

  • kmp算法!

感谢大家的阅读,我是Alson_Code,一个喜欢把简单问题复杂化,把复杂问题简单化的程序猿! ❤