代码随想录第九天|●28. 实现 strStr()●459.重复的子字符串●字符串总结 ●双指针回顾

本文详细介绍了KMP算法,包括其基本思想、前缀表的构造、next数组的计算与使用,以及如何在strStr问题中实现高效的字符串查找,展示了KMP算法在字符串搜索中的应用和时间复杂度分析。
摘要由CSDN通过智能技术生成

本次两道题都是KMP相关 马上毕设中期,后续补充

在一个串中查找是否出现过另一个串,这是KMP的看家本领。

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

本篇将以如下顺序来讲解KMP,

  • 什么是KMP
  • KMP有什么用
  • 什么是前缀表
  • 为什么一定要用前缀表
  • 如何计算前缀表
  • 前缀表与next数组
  • 使用next数组来匹配
  • 时间复杂度分析
  • 构造next数组
  • 使用next数组来做匹配
  • 前缀表统一减一 C++代码实现
  • 前缀表(不减一)C++实现
  • 总结

28.实现 strStr(()

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

思路

本题算是KMP最经典的运用,在一个串里查找是否出现过另一个串。

常规迭代也可以解决

常规迭代

遍历文本串,当出现不匹配时 文本串回退已匹配的长度,模式串回退到开头位置。

class Solution {
    /**
     * 基于窗口滑动的算法
     * <p>
     * 时间复杂度:O(m*n)
     * 空间复杂度:O(1)
     * 注:n为haystack的长度,m为needle的长度
     */
    public int strStr(String haystack, String needle) {
        int m = needle.length();
        // 当 needle 是空字符串时我们应当返回 0
        if (m == 0) {
            return 0;
        }
        int n = haystack.length();
        if (n < m) {
            return -1;
        }
        int i = 0;
        int j = 0;
        while (i < n - m + 1) {
            // 找到首字母相等
            while (i < n && haystack.charAt(i) != needle.charAt(j)) {
                i++;
            }
            if (i == n) {// 没有首字母相等的
                return -1;
            }
            // 遍历后续字符,判断是否相等
            i++;
            j++;
            while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
                i++;
                j++;
            }
            if (j == m) {// 找到
                return i - j;
            } else {// 未找到
                i -= j - 1;
                j = 0;
            }
        }
        return -1;
    }
}

KMP(-1)

 kmp解法主要解决两个问题,一是构造next数组,二是根据next数组去匹配

构造next数组

我们定义一个函数getNext来构建next数组,函数参数为next数组,和一个字符串。 代码如下:

    public void getNext(int [] next, String s){
        
    }

        构造next数组其实就是计算模式串s,前缀表的过程,分为三步

1、初始化

2、处理前后缀不相同的情况

3、处理前后缀相同的情况

详解:

1、 初始化:

        定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置,然后对next数组进行初始化赋值,如下:

        int j = -1;
        next[0] = j;

j 为什么要初始化为 -1呢,因为 前缀表要统一减一的操作仅仅是其中的一种实现,我们这里选择 j  初始化为-1,下文我还会给出 j 不初始化为-1的实现代码。

next[i] 表示 i (包括i)之前最长相等前后缀长度 (其实就是 j )

所以初始化next[0] = j

2、处理前后缀不相同的情况

因为j初始化为-1,那么 i 就从 1 开始,进行 s[i] 与 s [j + 1] 的比较

所以遍历模式串 s 的循环下标 i 要从 1 开始,代码如下:

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

如果s[i] 与 s[j+1] 不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退,

怎么回退呢?

next[j] 就是记录着j (包括j)之前的子串的相同前后缀的长度

那么s[i] 与s[j + 1]不相同,就要找 j+1 前一个元素在next数组里的值(就是next[j]) 

所以,处理前后缀不相同情况的代码如下:

            //处理前后缀不相等的情况
            while(j >= 0 && next[i] != next[j+1]){
                j = next[j]; //向前回退
            }

 3、处理前后缀相同情况

如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

代码如下

            //处理前后缀相等的情况
            if(next[i] == next[j+1]){
                j++;
            }
            next[i] = j;

所以,构造next数组的函数代码如下:

    public void getNext(int [] next, String s){
        // j 指向前缀末尾位置 i 指向后缀末尾位置
        // next[i] 表示 i (包括i) 之前最长相等的前后缀长度(其实就是 j)
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.length(); i++){
            //处理前后缀不相等的情况
            while(j >= 0 && next[i] != next[j+1]){
                j = next[j]; //向前回退
            }
            //处理前后缀相等的情况
            if(next[i] == next[j+1]){
                j++;
            }
            next[i] = j;
        }
    }

 根据next数组去匹配:

在文本串s里 找是否出现过模式串t。

定义两个下标 i 和 j ,j 指向模式串起始位置,i指向文本串起始位置。

那么j初始值依然为-1,为什么呢? 依然因为next数组里记录的起始位置为-1。

i就从0开始,遍历文本串,代码如下:

for(int i = 0; i < haystack.length(); i++)

接下来就是文本串与模式串的比较(s[i] 与 t[j + 1] j 从 -1 开始) 

如果 不相同 j就要从next数组里找下一个匹配的位置

代码如下

            while(j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)){
                j = next[j];//回退
            }

 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:

            if(haystack.charAt(i) == needle.charAt(j + 1)){
                j++;
            }

如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。

本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

代码如下:

            if(j == needle.length() - 1){
                return i - needle.length() + 1;
            }

那么,利用next数组,使用模式串匹配文本串的整体代码如下:

 

        int j = -1;//因为next数组里记录的初始位置为-1
        for(int i = 0; i < haystack.length(); i++){//i从0开始
            //不匹配
            while(j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)){
                j = next[j];//回退,j寻找之前匹配的位置
            }
            //匹配,i与j同时向后移动
            if(haystack.charAt(i) == needle.charAt(j + 1)){
                j++;
            }
            //文本串里出现了模式串
            if(j == needle.length() - 1){
                return (i - needle.length() + 1);
            }
        }

代码

getNext中添加了部分输出 以更好理解计算next数组的过程

class Solution {
    /**
     * 基于kmp的算法
     * <p>
     * 时间复杂度:O(m + n)
     * 空间复杂度:O(m)
     * 注:n为haystack的长度,m为needle的长度
     */
    public void getNext(int [] next, String s){
        // j 指向前缀末尾位置 i 指向后缀末尾位置
        // next[i] 表示 i (包括i) 之前最长相等的前后缀长度(其实就是 j)
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.length(); i++){
            System.out.println("i = " + i);
            //处理前后缀不相等的情况
            while(j >= 0 && s.charAt(i) != s.charAt(j + 1)){
                j = next[j]; //向前回退
                System.out.println("回退" + j);
            }
            //处理前后缀相等的情况
            if(s.charAt(i) == s.charAt(j + 1)){
                j++;
                System.out.println("相等" + j);
            }
            next[i] = j;
            System.out.println(next[i]);
        }
    }
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0){
            return 0;
        }
        int len = needle.length();
        int [] next = new int [len];
        getNext(next, needle);

        int j = -1;//因为next数组里记录的初始位置为-1
        for(int i = 0; i < haystack.length(); i++){//i从0开始
            //不匹配
            while(j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)){
                j = next[j];//回退,j寻找之前匹配的位置
            }
            //匹配,i与j同时向后移动
            if(haystack.charAt(i) == needle.charAt(j + 1)){
                j++;
            }
            //文本串里出现了模式串
            if(j == needle.length() - 1){
                return (i - needle.length() + 1);
            }
        }
        return -1;
    }
}

459.重复的子字符串

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值