算法打卡day8|字符串篇02|Leetcode 28. 找出字符串中第一个匹配项的下标、459. 重复的子字符串

算法题

Leetcode 28. 找出字符串中第一个匹配项的下标

题目链接:28. 找出字符串中第一个匹配项的下标

大佬视频讲解:KMP理论篇

KMP代码篇

个人思路

当看到在一个串中查找是否出现过另一个串,那肯定是用kmp算法了; kmp比较难理解,详细理论和代码可以看上面参考;

按照个人理解 kmp就是帮忙在 查找串时减少不必要的检索;

用一个叫做前缀表(next)的数组先来找到 最长相等前后缀的位置, 然后 双指针检索 目标字符串,在源字符串 字母与目标字符串字母相同 时,往前走,不同时找前缀表对应位置回退;

解法
KMP算法

n为目标串长度,m为源串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组 ,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。而暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。

其中前缀表有多种方式生成,这里采用 不减一的情况.

java 复制代码
class Solution {
    
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);//用源串needle 生成前缀表

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {//遍历目标串

            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) 
            //字符不同时,按照前缀表回退 即从next数组里寻找下一个匹配的位置
                j = next[j - 1];
            if (needle.charAt(j) == haystack.charAt(i)) 
                j++;//字符相同,那么i 和 j 同时向后移动

            //如果j指向了模式串t的末尾,那么就说明源串完全匹配目标串里的某个子串了
            if (j == needle.length()) 
                return i - needle.length() + 1;//返回第一个匹配到串的位置
        }
        return -1;

    }
    
//前缀表(不减一)
    private void getNext(int[] next, String s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {//i从1开始才能与j开始对比

            while (j > 0 && s.charAt(j) != s.charAt(i)) // 前后缀不相同时
                j = next[j - 1];// 向前回退

            if (s.charAt(j) == s.charAt(i)) // 找到相同的前后缀
                j++;
            next[i] = j; // 将j(前缀的长度)赋给next[i]
        }
    }
}

时间复杂度:O( n+m**)**;(双指针遍历字符串全部)

空间复杂度:O( m**);**(只需要保存字符串needle的前缀表)

Leetcode459. 重复的子字符串

题目链接:459. 重复的子字符串

大佬视频讲解:459. 重复的子字符串

个人思路

思路不清晰

解法
移动匹配

题目难度主要在于如何分辨字符串是否能由子串重复构成;

当一个字符串s:godgod,内部由重复的子串组成,那么这个字符串的结构一定是这样的:

s是由前后相同的子串 组成。那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前面的子串做后串,就一定还能组成一个s,如图:

所以判断字符串s是否由重复子串组成,只要两个s拼接在一起, 刨除 s + s 的首字符和尾字符(避免在s+s中搜索出原来的s),里面还出现一个s的话,就说明是由重复子串组成.

java 复制代码
class Solution {
    public boolean repeatedSubstringPattern(String s) {
//indexOf(s, 1)查找字符串 s 第一次出现的位置,从索引1开始搜索(即从原字符串的第二个字符开始搜索)


        return (s + s).indexOf(s, 1) != s.length();
    }
}

时间复杂度:O(n);(查找索引)

空间复杂度:O(1);(没使用辅助空间)

KMP算法

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

先放结论**, 数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。**

这样直接说有点抽象,举个例子:

next[len - 1] = 8,8就是此时字符串goodgoodgoodgood的最长相同前后缀的长度。(前缀不包括最后一个字符,后缀不包括第一个字符)

(len - (next[len - 1] )) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(good)。

java 复制代码
class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s.equals("")) return false;

        int len = s.length();
        // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
        s = " " + s;

        char[] chars = s.toCharArray();//字符串转换为字符数组
        int[] next = new int[len + 1];//前缀表

        // 构造 next 数组过程,j从0开始(空格),i从2开始
        for (int i = 2, j = 0; i <= len; i++) {
            // 匹配不成功,j回到前一位置 next 数组所对应的值
            while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
            // 匹配成功,j往后移
            if (chars[i] == chars[j + 1]) j++;
            // 更新 next 数组的值
            next[i] = j;
        }

        // 判断是否是重复的子字符串
        if (next[len] > 0 && len % (len - next[len]) == 0) {
            return true;
        }
        return false;
    }
}

时间复杂度:O(n);(一个for循环)

空间复杂度:O(n);(next数组)

以上是个人的思考反思与总结,若只想根据系列题刷,参考卡哥的网址代码随想录算法官网代码随想录算法官网代码随想录算法官网

相关推荐
LYFlied2 分钟前
【每日算法】LeetCode 104. 二叉树的最大深度
前端·算法·leetcode·面试·职场和发展
arron88993 分钟前
以目标检测基础知识学习分割模型算法
学习·算法·目标检测
凤凰战士芭比Q3 分钟前
Jenkins(Pipeline job)
java·servlet·jenkins
IT方大同5 分钟前
循环结构的功能
c语言·数据结构·算法
代码不停13 分钟前
BFS解决拓扑排序和FloodFill问题
java·算法·宽度优先
TL滕15 分钟前
从0开始学算法——第二十一天(复杂链表问题)
笔记·学习·算法
Chengbei1119 分钟前
CVE-2025-24813 Tomcat 最新 RCE 分析复现
java·安全·web安全·网络安全·tomcat·系统安全·网络攻击模型
AAA简单玩转程序设计24 分钟前
救命!Java 进阶居然还在考这些“小儿科”?
java·前端
sin_hielo27 分钟前
leetcode 955
数据结构·算法·leetcode
总是学不会.30 分钟前
【JUC编程】多线程学习大纲
java·后端·开发