重复的子字符串
力扣题目链接(opens new window)
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。
示例 2:
输入: "aba"
输出: False
示例 3:
输入: "abcabcabcabc"
输出: True
解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)
考虑使用KMP,但是无法确定模式串(即,如何找出可重复的子串)
如果使用for循环去找,那么找到之后还需要再套一层for循环去匹配
复杂度爆了,因此不太可行
移动匹配不使用KMP算法也可以
思路大概是这样的
实际处理时,我们将两个字符串拼接之后,如何搜索是否再次出现原字符串呢?
只需要把拼接字符串掐头去尾,然后使用find查找即可
代码
class Solution { public: bool repeatedSubstringPattern(string s) { //拼接字符串 string mix = s + s; //掐头去尾 mix.erase(mix.begin()); mix.erase(mix.end() - 1); //在拼接串中找s if(mix.find(s) != string::npos){ return true; } return false; } };
注意:string::npos是作为有无匹配项的判别条件, 详见
KMP 思路 大体思路使用KMP算法也可以
回顾一下,KMP算法是用来干嘛的?找出一个字符串中是否存在目标子串
在这个过程中,我们需要去求模式串的前缀表并且构建next数组,当遇到字符不匹配的时候可以找到上一个匹配过的位置继续匹配。
前缀表中保存了以字符串各个位置为终点的 最长相同前后缀的长度 。
利用最长相同前后缀便可以求出重复子串
前后缀的定义先再次明确一下前后缀的定义:以"ABCDABD"为例( 参考 )
- "A"的前缀和后缀都为空集,共有元素的长度为0; - "AB"的前缀为[A],后缀为[B],共有元素的长度为0; - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0; - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0; - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1; - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2; - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
这里有个关键点:
前后缀是对于整个字符串来说的,而不是针对字符串中某个字符
一种经典错误思想是:"AB"中,B的前缀是A,A的后缀是B;
最小重复子串搞清楚上面的概念之后,以字符串"augaugaugaug"为例
你会发现, 由重复子串组成的字符串("augaugaugaug")中,最长相等前后缀不包含的子串("aug")就是最小重复子串
而KMP正好可以求这玩意(最长相等前后缀),巧了!
具体思路字符串"augaugaugaug"的长度,len = 12;
现在要引入next数组(计算方式 详见 ),如图所示(前缀表统一减一,即 j 的初始值为 -1)
如果 next[len - 1]不为-1,则说明字符串有最长相同的前后缀
获取最长相等前后缀的长度
该长度实际上就是next数组中下标为 len-1 位置的值,即 next[len-1]
因为是 从0开始算的,所以要加个1补回来 (本质上是由前缀表统一减一的缘故导致的),即 next[len-1]+1
用字符串数组的长度len减去该长度
得到一个子串M的长度 len - (next[len-1]+1)
然后用该子串长度再与len做取模运算
如果 能够整除 (即 len % (len - (next[len - 1] + 1)) == 0 ),说明 整个字符串都是由子串M组成的
代码class Solution { public: //创建获取计算next数组的函数 void getNext(int* next, string& s){ //初始化j和next int j = -1; next[0] = j; //开始遍历输入串 for(int i = 1; i < s.size(); ++i){//注意i从1开始 //不匹配,回退 while(j >= 0 && s[j + 1] != s[i]){ j = next[j]; } //匹配,将j值记录到next数组,两个指针后移 if(s[j + 1] == s[i]){ j++; } next[i] = j; } } bool repeatedSubstringPattern(string s) { //创建next数组 int next[s.size()]; //计算next数组 getNext(next, s); //求输入串的长度 int len = s.size(); if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {//条件不要漏了,如果本来字符串就没有最大相等前后缀也就不用判断了 return true; } return false; } };易错点
1、getNext函数中指针 i 从1开始,另外一个指针应该是 j+1 而不是 j
2、如果输入串都不存在最大相等前后缀,那么可以直接返回false,
即别忘了条件 next[len - 1] != -1
查看更多关于【LeetCode字符串#06】KMP巩固练习:重复子串的详细内容...