问题
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000
示例 1:
输入: “abab”
输出: True
解释: 可由子字符串 “ab” 重复两次构成
示例 2:
输入: “aba”
输出: False
示例 3:
输入: “abcabcabcabc”
输出: True
解释: 可由子字符串 “abc” 重复四次构成。 (或者子字符串 “abcabc” 重复两次构成。)
思路
这里就要说一说next数组了,next 数组记录的就是最长相同前后缀,如果next[len - 1] != -1
,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)
- 最长相等前后缀的长度为:
next[len - 1] + 1
- 数组长度为:
len
如果len % (len - (next[len - 1] + 1)) == 0
,则说明 (数组长度-最长相等前后缀的长度
) 正好可以被数组的长度
整除,说明有该字符串有重复的子字符串
强烈建议大家把next数组打印出来,看看next数组里的规律,有助于理解KMP算法
如图:
此时next[len - 1] = 7
,next[len - 1] + 1 = 8
,8就是此时 字符串asdfasdfasdf
的最长相同前后缀的长度。(len - (next[len - 1] + 1))
也就是:12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)
解法一:KMP
package leetcode_459;
import java.util.Arrays;
public class solution {
public boolean repeatedSubstringPattern(String s) {
return kmp(s);
}
public boolean kmp(String pattern){
int length = pattern.length();
int[] a = new int[length];
Arrays.fill(a, -1);
for(int i = 1; i < length; i++){
int j = a[i - 1];
while(j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)){
j = a[j];
}
if(pattern.charAt(j + 1) == pattern.charAt(i)){
a[i] = j + 1;
}
}
return a[length - 1] != -1 && length % (length - a[length - 1] - 1) == 0;
}
}
- 时间复杂度:
- 空间复杂度:
方法二:枚举
如果一个长度为n
的字符串s
可以由它的一个长度为n'
的子串s'
重复多次构成,那么:
n
一定是n'
的倍数s'
一定是s
的前缀- 对于任意的
,有
也就是说,s
中长度为n'
的前缀就是s'
,并且在这之后的每一个位置上的字符s[i]
,都需要与它之前的第n'
个字符s[i-n']
相同
因此,我们可以从小到大枚举n'
,并对字符串s
进行遍历,进行上述的判断。注意到一个小优化是,因为子串至少需要重复一次,所以n'
不会大于n
的一半,我们只需要在的范围内枚举
n'
即可
class Solution {
public boolean repeatedSubstringPattern(String s) {
int n = s.length();
for (int i = 1; i * 2 <= n; ++i) {
if (n % i == 0) {
boolean match = true;
for (int j = i; j < n; ++j) {
if (s.charAt(j) != s.charAt(j - i)) {
match = false;
break;
}
}
if (match) {
return true;
}
}
}
return false;
}
}