代码随想录二刷 | 字符串 |重复的子字符串
- 题目描述
- 解题思路 & 代码实现
- 移动匹配
- KMP算法
题目描述
459.重复的子字符串
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = “abab”
输出: true
解释: 可由子串 “ab” 重复两次构成。
示例 2:
输入: s = “aba”
输出: false
示例 3:
输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)
提示:
- 1 <= s.length <= 104
- s 由小写英文字母组成
解题思路 & 代码实现
移动匹配
如果一个字符串是由重复的子串构成,也就是由前后相同的子串构成。那么用后面的子串做前串,前面的子串做后串,也依然能组成原来的字符串。
所以判断字符串 s 是否由重复子串组成,只要两个s拼在一起里面还能出现一个s,就说明s是由重复子串组成的。
在判断 s + s 拼接的字符串时,要刨除 s + s 的首字符和尾字符,这样避免在 s + s中搜索出原来的s,毕竟我们要搜索的是中间拼接出来的 s。
class Solution {
public:bool repeatedSubstringPattern(string s) {string t = s + s;t.erase(t.begin());t.erase(t.end() - 1);if (t.find(s) != std::string::npos)return true;return false;}
};
KMP算法
在重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,拿字符串s = "abababab"
举例:
假设字符串 s 由 n 个重复子串构成,重复子串的长度为x,所以 s = n * x
因为字符串 s 的最长公共前后缀的长度一定不是 s 本身,所以最长相同前后缀的长度为m * x
,而且n - m = 1
。这里可以从上图中看出,n 和 m 正好相差一个 x 的长度。
所以,如果nx % (n - m) * x = 0
,说明s的长度可以整除一个重复子串的长度,也就说明有重复出现的子字符串。
假设数组的长度为len,那么最长公共前后缀的长度就是next[len - 1] + 1
,这里+1是因为采用了统一减一的操作。
那么,数组的长度 - 最长公共前后缀就是一个x的长度,只要数组的长度能整除 x 的长度,那么就存在重复的子字符串。
那么,套用上面的nx % (n - m) * x
,可得len % (len - (next[len - 1] + 1)) == 0
class Solution {
public:void getNext(int* next, const string& s) {int j = -1;for (int i = 1; i < next.size(); i++) {while (j >= 0 && next[i] != next[j + 1]) {j = next[j];}if (next[i] == next[j + 1]) {j++;}next[i] = j;}}bool repeatedSubstringpattern(string s) {if (s.size() = 0) {return false;}int next[s.size()];getNext(next, s);len = s.size();if (next[len - 1] + 1 != 0 && len % (len - (next[len - 1] + 1)) == 0) {return true;}return false;}
};