刷题计划day9 字符串 下【KMP再续】【找出字符串中第一个匹配项的下标】【重复的子字符串】

⚡刷题计划day9继续,可以点个免费的赞哦~

往期可看专栏,关注不迷路,

您的支持是我的最大动力🌹~

今天的重头戏是KMP算法的理解,如果是第一次接触可能会有难度,可以结合以下链接的讲解及对应视频,没有明白可以多看多听几遍,加油!然后下一期就是双指针专题了,欢迎点赞收藏关注!

(programmercarl.com)

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

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

(https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/)

法一:一般思路

法一就是暴力解,直接遍历两个串,比较是否匹配。

详见注释

class Solution {
    public int strStr(String haystack, String needle) {
        // 获取haystack和needle的长度
        int n1 = haystack.length();
        int n2 = needle.length();
​
        // 如果haystack的长度小于needle的长度,直接返回-1,表示找不到
        if(n1 < n2){
            return -1;
        }
​
        // 将haystack和needle转换为字符数组
        char[] c1 = haystack.toCharArray();
        char[] c2 = needle.toCharArray();
​
        // 遍历haystack
        for(int i = 0; i < n1; i++){
            // 初始化搜索的起始和结束索引
            int start = i;
            int end = i + n2 - 1;
            // 如果结束索引超出haystack的范围,跳出循环
            if(end >= n1){
                break;
            }
​
            // 初始化一个标志位,用于判断是否找到匹配的子串
            boolean flag = true;
            int k = 0;
            // 内层循环,用于比较字符
            while(start <= end){
                // 如果当前字符不匹配,设置标志位为false并跳出循环
                if(c1[start++] != c2[k++]){
                    flag = false;
                    break;
                }
            }
            // 如果标志位为true,表示找到了匹配的子串,返回当前索引i
            if(flag){
                return i;
            }
        }
​
        // 如果遍历完haystack都没有找到needle,返回-1
        return -1;
    }
}

法二:KMP算法

详见注释

class Solution {
    // getNext 方法用于构建 next 数组,该数组用于存储模式字符串的最长前后缀匹配的长度
    public void getNext(int[] next, String s) {
        int j = -1; // j 用于记录当前比较的子字符串的最后一个匹配位置
        next[0] = j; // 模式字符串的第一个字符没有前缀,所以它的 next 值是 -1
        for (int i = 1; i < s.length(); i++) { // 从模式字符串的第二个字符开始遍历
            while (j >= 0 && s.charAt(i) != s.charAt(j + 1)) { // 当前字符不匹配时,尝试回溯
                j = next[j]; // 根据 next 数组回溯到上一个匹配的位置
            }
            if (s.charAt(i) == s.charAt(j + 1)) { // 如果当前字符匹配,更新 j
                j++;
            }
            next[i] = j; // 更新 next 数组,存储当前位置的最长前后缀匹配的长度
        }
    }
​
    // strStr 方法用于在 haystack 字符串中查找 needle 字符串出现的位置
    public int strStr(String haystack, String needle) {
        if (haystack.length() < needle.length()) { // 如果文本字符串比模式字符串短,直接返回 -1
            return -1;
        }
​
        int[] next = new int[needle.length()]; // 创建 next 数组
        getNext(next, needle); // 构建 next 数组
​
        int j = -1; // 初始化 j 为 -1,表示在模式字符串中的位置
        for (int i = 0; i < haystack.length(); i++) { // 遍历文本字符串
            while (j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)) { // 如果当前字符不匹配,根据 next 数组回溯
                j = next[j]; // 回溯到上一个匹配的位置
            }
            if (haystack.charAt(i) == needle.charAt(j + 1)) { // 如果当前字符匹配,更新 j
                j++;
            }
            if (j == needle.length() - 1) { // 如果 j 到达模式字符串的末尾,表示找到了匹配
                return (i - needle.length() + 1); // 返回模式字符串在文本字符串中的起始位置
            }
        }
​
        return -1; // 如果遍历完文本字符串都没有找到匹配,返回 -1
    }
}

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

459.重复的子字符串

(https://leetcode.cn/problems/repeated-substring-pattern/description/)

法一:暴力解法

解题思路:

如果字符串 S 包含一个重复的子字符串,则可以通过多次移位原字符串,最终能得到一新相同字符串与原字符串匹配。

例:abcabc

移位一次:cabcab 移位两次:bcabca 移位三次:abcabc

现在字符串和原字符串匹配了,所以可以得出结论存在重复的子串。

但是对于重复字符串比较长的情况,效率就会很低,并且超时。

基于此思想,可以新建一个字符串str = s + s,等于原字符串s再加上自身,这样新的字符串str就包含了所有移动的情况。

比如字符串:s = acd,那么 str = s + s = acdacd

acd 移动的可能:dac、cda。其实都包含在了 str 中了。就像一个滑动窗口

一开始 acd (acd) ,移动一次 ac(dac)d,移动两次 a(cda)cd。循环结束.

注意需要去除 str 中首尾元素

 所以可以直接判断之后,是否包含自身元素。如果包含。则表明存在重复子串。

附一网友理解:如果S不包含重复的子字符串,则S本身就是所谓的“重复子字符串”(这里方便自己理解,视为重复一次,不深究= =),str = S+S,说明S是str的重复子字符串,刨去str首尾两个字符之后(相当于分别破坏前一个S头部和后一个S尾部),不能满足str包含S。

AC代码

class Solution {
    public boolean repeatedSubstringPattern(String s) {
			String t = s + s;
			t = t.substring(1, t.length() - 1); 	// 掐头去尾
			if (t.contains(s)) {
				return true;
			}
			return false;
    }
}

法二:KMP

关于kmp还不是很理解的可以再康康上期附送的链接,这里不再多叙述

然后此题需要证明,有兴趣的小伙伴也可以康康这个链接:

(https://programmercarl.com/0459.重复的子字符串.html#思路)

我们这里直接给出判断的结论:

如果len % (len - (next[len - 1] + 1)) == 0 ,即数组的长度正好可以被 最长相等前后缀不包含的子串的长度 整除 ,说明该字符串有重复的子字符串。

class Solution {
    public void getNext(int[] next,String s){
        int j=-1;
        next[0]=j;
        for(int i=1;i<s.length();i++){
            while (j>=0 && s.charAt(i)!=s.charAt(j+1)){
                j = next[j];
            }
            if(s.charAt(i)==s.charAt(j+1)){
                j++;
            }
            next[i] = j;
        }
    }
    
    public boolean repeatedSubstringPattern(String s) {
        if(s.equals("")) return false;
        int len = s.length();
        int j=-1;
        int[] next = new int[len];
        getNext(next,s);
        //
        //1.如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀
        //2.最长相等前后缀的长度为:next[len - 1] + 1。(这里的next数组是以统一减一的方式计算的,因此需要+1
        //3.len - (next[len - 1] + 1) 是最长相等前后缀不包含的子串的长度。
        if(next[len-1] != -1 && len % (len-(next[len-1]+1))==0){
            return true;
        }
        return false;
    }
}

下期开启新专题,感谢点赞收藏关注哦🌹

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值