题目描述:
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
示例1:
输入: s = “abab”
输出:true
解释: 可由子串 “ab” 重复两次构成。
示例2:
输入: s = “aba”
输出: false
示例3
输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)
思路
暴力解法:
用一遍for循环遍历子字符串的终止位置,枚举出子字符串,然后和母字符串匹配,若刚好能重复构成母字符串就返回true。不能则返回false。
这里选取终止位置最大可以只取到母字符串的一半,因为子字符串要在母字符串中重复,最大长度不超过母字符串的一半。
移动匹配:
当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:
也就是由前后相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:
所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
kmp解法:
next数组可以得出当前字符串的最大前后缀长度,如果s是循环字符串,那么next[len]一定是它最大循环字符子串的长度,总的字符串长度一定是循环子串长度的倍数,此时只要我们只要判断len能否被len - next[len]整除即可。
原因:
下图中绿色箭头表示箭头两向的字符都是相等的(因为是最长公共前后缀)。由于s[0]=t[0],t[0]=k[0], k[0]=s[2],即s[0]=s[2],同理可得出s[1]=s[3]。证明两公共前后缀交错的那部分是最小重复子串。
代码
暴力解法:
bool repeatedSubstringPattern(string s) {
if (s.size() == 0)return false;
for (int i = 1; i <= s.size() / 2; i++) {
if (i % 2 != 0)continue;
string t = s.substr(0, i);//i代表每次截取的字符串的长度
bool flag = true;
for (int j = i; j < s.size(); j+=i) {
string n = s.substr(j, i);
if (n != t)
{
flag = false;//表示此次i选取的子字符串不能重复构成母字符串
break;
}
}
if (flag)return true;//表示此次i选取的子字符串能重复构成母字符串
}
return false;
}
移动匹配解法:
bool repeatedSubstringPattern(string s) {
string t = s + s;
t.erase(t.begin());
t.erase(t.end() - 1);
if (t.find(s) != -1)return true;
else return false;
}
kmp算法:
void getNext(int* next, string& s) {
int i, j = 0;
next[0] = j;
for (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) {
if (s.size() == 0)return false;
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] && len % (len - next[len - 1]) == 0)return true;
return false;
}