代码随想录算法训练营第九天 | 28. 实现 strStr()、 459.重复的子字符串

学习目标

  • 完成KMP相关题目

这里强烈推荐Carl哥的:帮你把KMP算法学个通透!(理论篇)帮你把KMP算法学个通透!(求next数组代码篇),可以说讲得十分通透了


学习内容

KMP算法

首先了解最长相等前后缀及其作用:
在这里插入图片描述
求 next 数组:
在这里插入图片描述
那么对 aabaaf来说其 next 数组就是[0, 1, 0, 1, 2, 0]。当然在使用过程中,为了方便使用,会做统一右移或整体-1的操作,是KMP算法的具体实现,不涉及前后缀,最终使用效果一致。统一右移得到-1, 0, 1, 0, 1, 2,整体-1得到[-1, 0, -1, 0, 1, -1]

对于求解的图片,这里引用 Carl 在 28. 实现 strStr()中的动图:
在这里插入图片描述
j (或者说 j+1,因为这里求的是整体-1的 next 数组)指向的是前置的最末尾,同时也是前缀的长度
i 指向后缀的末尾,其实就是双指针中用来迭代的指针。
可以写(抄)出其这个动图的代码,确实很抽象:

private int[] getNext(String s) {
	int j = -1;
	int[] next = new int[s.length()];
	next[0] = j;
	
	//i从1开始才能比较最长相等前后缀
	for(int i = 1; i < s.length(); i++) {	

		//前缀末尾和后缀末尾不匹配时候,看 next 数组前一位,即前一个子串前缀末尾和后缀末尾匹配时候的位置
		//这是因为,例如aabcaaf,前一个子串是aabcaa,f不匹配必然要从前缀的aa后开始找f是否匹配前缀aa后的字符
		//tip:这是一个循环,因为可能不能一次找到,如上面例子
		while(j >= 0 && s.charAt(j+1) != s.charAt(i)) {
			j = next[j];
 		}
 		
 		//匹配了,那么前缀的长度 + 1,或者说是最长前缀的末尾的位置向右移动一格
 		if (s.charAt(j+1) == s.charAt(i)) {
			j ++;
		}
		//记录next[i]对应的字符串的最长相等前后缀的长度
		next[i] = j;
	}
	return next;
}

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

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

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

提示:
1 <= haystack.length, needle.length <= 104
haystack 和 needle 仅由小写英文字符组成

思路:KMP
时间复杂度:O(n+m) 空间复杂度:O(n)

解决方案:

class Solution {
    public int strStr(String haystack, String needle) {
        int[] next = getNext(needle);
        int j = -1;
        for (int i = 0; i < haystack.length(); i++) {
            while (j >= 0 && haystack.charAt(i) != needle.charAt(j+1)) {
                j = next[j];
            }
            if (haystack.charAt(i) == needle.charAt(j + 1)) {
                j ++;
            }
            if (j == needle.length() - 1) {
                return i - j;
            }
        }

        return -1;
    }

    public int[] getNext(String s) {
        int[] next = new int[s.length()];
        int j = -1;
        next[0] = -1;
        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;
        }

        return next;
    }
}

小结:KMP 比较抽象,一是要理解,二是要多复习。


459. 重复的子字符串(Easy)

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

题目:给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:
输入: s = “abab”
输出: true
解释: 可由子串 “ab” 重复两次构成。
示例 2:
输入: s = “aba”
输出: false
示例 3:
输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)

提示:
1 <= s.length <= 104
s 由小写英文字母组成

思路:依稀记得这题目好像不是 Easy 难度的,但这种解法出来后就变成了 Easy 难度。当成看到也是亮瞎眼。
时间复杂度:O(n*m) 空间复杂度:O(n)

解决方案:

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        String sa = s.substring(1, s.length()) + s.substring(0, s.length() - 1);
        return sa.contains(s);
    }
}

小结:上面的算法中String.contains(String s)用的不是 KMP 算法。这里为了练习 KMP,参照题解写,主要思想:

  1. 获取 next 数组 (这里获取各个元素 -1 的 next 数组)
  2. 字符串长度 % (字符串长度 - (next[字符串长度 - 1] + 1)) == 0,且 next[字符串长度 - 1] 不能是 -1
    也就是字符串长度能整除字符串减去其最大公共前缀长度后的长度,next[字符串长度 - 1] 是 -1 说明没有相同前后缀,那比不会由子串重复构成。next[字符串长度 - 1] + 1是指 next 数组的最后一位,且由于是获取各个元素 -1 的 next 数组,因此要 +1.
class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int[] next = getNext(s);
        if (next[s.length() - 1] > -1 && s.length() % (s.length() - (next[s.length() - 1] + 1)) == 0) {
            return true;
        }
        return false;
    }

    private int[] getNext(String s) {
        int[] next = new int[s.length()];
        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;
        }   
        return next;
    }
}

总结

  • KMP 比较抽象,要理解,多复习。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值