题目
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1: 输入: “abab” 输出: True 解释: 可由子字符串 “ab” 重复两次构成。
示例 2: 输入: “aba” 输出: False
示例 3: 输入: “abcabcabcabc” 输出: True 解释: 可由子字符串 “abc” 重复四次构成。 (或者子字符串
“abcabc” 重复两次构成。)
思路
方法一
在一个串中查找是否出现过另一个串——>KMP算法
前缀表里统计了各个位置为终点字符串的最长相同前后缀的长度。(具体实现时:next数组=前缀表长度-1)
若next[len - 1] != -1,则说明字符串有最长的相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。
最长相等前后缀的长度为:next[len - 1] + 1
因为这里next数组是以前缀表减一的方式计算
因为字符串s的最长相同前后缀的的长度一定是不包含s本身,所以 最长相同前后缀长度必然是(n-1) * x
即数组的长度减去最长相同前后缀的长度相当于是一个最小重复子串的长度(即一个周期的长度),如果这个周期可以被数组长度整除,说明整个数组就是这个周期的循环。
len % (len - (next[len - 1] + 1)) == 0
java代码如下:
class Solution {
public boolean repeatedSubstringPattern(String s){
if(s.equals("")) return false;
int len = s.length();
//原串加个空格(哨兵),让下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();//转化成字符数组
int[] next = new int[len-1];//创建next数组
//构造next数组,j从0开始,i从2开始
for(int i =2,j = 0; i < len; i++){
//匹配不成功,j回退到前一位置next数组所对应的值
while(j > 0 && chars[i] != chars[j+1]) j = next[j];
//匹配成功,i、j同时后移
if(chars[i] == chars[j+1]) j++;
//将最大相同前后缀长度j更新给next数组
next[i] = j;
}
//最后判断是否是重复的子字符串,这里next[len]即代表数组末尾的值,因为加了一个空格
if(next[len] > 0 && (len % next[len]) == 0){
return true;
}
return false;
}
}
方法二
如果字符串 S 包含一个重复的子字符串,那么这意味着可以多次 “移位”`字符串,并使其与原始字符串匹配。
其中避免一些无用的环绕,可以创建一个新的字符串 str
,它等于原来的字符串 S 再加上 S 自身,这样其实就包含了所有移动的字符串
比如字符串:S = acd
,那么 str = S + S = acdacd
acd
移动的可能:dac、cda
。其实都包含在了 str
中了,就像一个滑动窗口
一开始 acd (acd)
,移动一次 ac(dac)d
,移动两次 a(cda)cd
。循环结束
所以可以直接判断 str
中去除首尾元素之后,是否包含自身元素。如果包含。则表明存在重复子串。
class Solution{
public boolean repeatedSubstringPattern(String s) {
String str = s+s;
str = str.substring(1,str.length() - 1);
return str.contains(s);
}
}