每日一题
重复的子字符串
题目链接
注:本题的题解基本建立在KMP算法之上,对KMP算法不太了解的小伙伴可以参考这篇文章KMP算法及其改进图文详解
方法一:移动匹配
我们先来看几个可以由一个字串重复多次构成的主字符串:“aaa”,“ababab”,“abcabc”
可以发现满足条件主串的两个规律:
- 一定可以在其前半部分和后半部分找到相等的子串,如上面三个例子的子串“a”,子串“ab”,子串“abc”
- 交换前半部分和后半部分都相等的子串,主串仍然不变。
由此可以得到,如果我们将主串复制一份,再将这两个相同的串拼到一起,并去头去尾(防止查找的过程中找到原来的串和复制后的串),如果能在新串中找到和主串相等的子串,那就说明这个主串就可以由它的一个子串重复构成
例如字符串abcabc
实现代码
//获得Next数组
void GetNext(char *s,int *Next)
{
int len = strlen(s);
Next[0] = -1;
int i = 0;
int k = -1;
while(i < len - 1)
{
if(k == -1 || s[i] == s[k])
{
k++;
i++;
if(s[i] == s[k])
Next[i] = Next[k];
else
Next[i] = k;
}
else
k = Next[k];
}
}
//判断是否可以在字符串s1中找到串s2
bool KMP(char *s1, char *s2, int *Next) //s1为主串,s2为从串
{
int len1 = strlen(s1);
int len2 = strlen(s2);
int i = 0, j = 0;
while(i < len1 && j < len2)
{
if(s1[i] == s2[j])
{
i++;
j++;
}
else if(j != 0)
{
j = Next[j];
if(j == -1)
j = 0;
}
else
i++;
}
if(j >= len2)
return true;
else
return false;
}
bool repeatedSubstringPattern(char * s){
int i,j;
int len = strlen(s);
char *str = (char *)malloc(sizeof(char) * 2 * len + 1);
int *Next = (int *)malloc(sizeof(int) * len);
GetNext(s,Next); //获得Next数组
//将串s和其复制串合并到str中
for(i = 0; i < len; i++)
str[i] = s[i];
j = i;
for(i = 0; i < len; i++)
str[j + i] = s[i];
str[2 * len] = '\0';
//去尾
str[2 * len - 1] = '\0';
return KMP(str + 1,s,Next); //str+1相当于去头
}
方法二:KMP
- 我们知道,KMP算法中,有Next数组保存了每个字符之前的子串的最长相等前后缀长度,那么最长相等前后缀长度和最小的重复子串有什么关系呢?
- 直接下结论:
- 如果一个字符串是由重复的子串构成,那么最长相等前后缀不包含的子串就是最小的重复子串
- 如果一个字符串没有相等前后缀,那么这个字符串一定不能由重复子串构成
- 由于本篇Next数组记录的是每个字符之前的子串的最长相等前后缀长度,因此为了判断该字符串是否具有相等的前后缀,需要在字符串的末尾再添加一个字符(随便什么字符都可以),这样,如果字符串没有相等的前后缀,那么Next[len - 1] = 0(len为Next数组大小)
- 字符串的最大相等前后缀长度为Next[len - 1],由第一个结论可得,最小重复子串为
len - next[len - 1]
,若len % (len - next[len - 1]) == 0
,就说明字符串长度可以整除最小重复子串的长度,即可说明该字符串可以由重复子串构成。
实现代码
//求Next数组
void GetNext(char *s,int *Next)
{
int len = strlen(s);
Next[0] = -1;
int i = 0;
int k = -1;
while(i < len - 1)
{
if(k == -1 || s[i] == s[k])
{
k++;
i++;
Next[i] = k;
}
else
k = Next[k];
}
}
bool repeatSubstring(char* str ) {
int len = strlen(str);
//创建一个比原字符串大两个字符(一个随机字符,和空字符)的字符数组
char* s = (char*)malloc(sizeof(char) * (len + 2));
//将原字符串拷贝到新字符串中,并添加随机字符和空字符
strcpy(s,str);
s[len] = 'a';
s[len + 1] = '\0';
//申请Next数组的内存
int *Next = (int *)malloc(sizeof(int) * (len + 1));
//得到Next数组
GetNext(s,Next);
//如果该字符串由相等前后缀,并且字符串长度可以整除最小重复子串,那么就说明这个字符串可以由重复子串构成
//这里的len之所以不要减一,是因为Next的大小为len+1,我们要得到的就是Next的最后一个值,即Next[len]
if(Next[len] != 0 && len % (len - Next[len]) == 0)
return true;
return false;
}