一.题目描述
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba" 输出: false
示例 3:
输入: s = "abcabcabcabc" 输出: true 解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
二.思路
KMP算法主要用途是查询一个字符串是否在另一个字符串中,在本题,在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位
如何找到最小重复子串?
1.求出next表,next表求法可以参考[c++算法学习-KMP算法-CSDN博客],根据出现重复子串的性质,最长公共前后缀出现在字符串末位,及len-1.
2.求最小重复子串,设其长度为m,当使用前缀表作为next数组时m=len-next[len-1],当使用前缀表-1作为next数组时,m=len-(next[len-1]+1)
3.判断是否可以由重复子串构成,如果可以构成则,len%m==0.
总结:数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。
例如:
next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。
三.代码
1.前缀表做next数组
class Solution {
public:
void getnext(int *next,string &s)
{
int j=0;
next[0]=j;
for(int i=1;i<s.size();i++)
{
while(j>0&&s[i]!=s[j])
j=next[j-1];
if(s[i]==s[j])
j++;
next[i]=j;
}
}
bool repeatedSubstringPattern(string s) {
int len=s.size();
if(len==0)
return false;
vector<int>next(len);
getnext(&next[0],s);
int m=len-next[len-1];//求最小重复子串
if(next[len-1]!=0&&len%(len-next[len-1])==0)//判断能否由最小字串组成
return true;
return false;
}
};
2.前缀表-1做next数组
lass Solution {
public:
void getNext (int* next, const string& s){
next[0] = -1;
int j = -1;
for(int i = 1;i < s.size(); i++){
while(j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if(s[i] == s[j + 1]) {
j++;
}
next[i] = j;
}
}
bool repeatedSubstringPattern (string s) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
return true;
}
return false;
}