LeetCode 459. 重复的子字符串
摘要
LeetCode 459 重复的子字符串题解,提供两种解法:移动匹配法(利用字符串拼接)和 KMP 算法法(利用前缀函数)。前者时间复杂度 O(n²),后者 O(n),空间复杂度均为 O(n)。
标签
#算法 #LeetCode #字符串 #KMP #题解
目录
-
题目描述
-
易错点
-
思路
- 解法一:移动匹配
- 解法二:KMP 算法
-
编写代码
1. 题目描述
给定一个非空的字符串 s,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
text
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
text
输入: s = "aba"
输出: false
示例 3:
text
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。(或子串 "abcabc" 重复两次构成。)
提示:
1 <= s.length <= 10^4s由小写英文字母组成
2. 易错点
- 字符串长度为 1 时,不可能由重复子串构成,直接返回
false - 移动匹配法中,拼接后的字符串去掉首尾字符时需注意索引范围
- KMP 算法中
next数组的构建需要正确理解前缀函数含义,避免越界 - 判断条件:若
len % (len - next[len-1] - 1) == 0且next[len-1] + 1 != len,则返回true,否则false - 注意不同
next数组定义(-1或0)对最终判断的影响
3. 思路
解法一:移动匹配
核心思想: 若字符串 s 可以由其子串重复多次构成,则 s 一定是 s + s 的子串。
将 s 与自身拼接得到 t = s + s。如果 s 是重复子串构成的,那么去掉 t 的首字符和尾字符后,剩余的字符串中一定包含 s。这是因为:
- 若
s = "abab",则t = "abababab",去掉首尾得"bababa",其中包含"abab"(从索引 1 开始)。 - 若
s不是重复子串构成,如"aba",则t = "abaaba",去掉首尾得"baab",不包含"aba"。
因此,只需判断 s 是否在 t.substr(1, 2*len-2) 中出现即可。该方法直接使用库函数 strstr 或手动匹配,时间复杂度取决于字符串查找算法,通常为 O(n²)。
解法二:KMP 算法
KMP 算法简介:
KMP 算法(Knuth-Morris-Pratt)主要用于字符串匹配,其核心是 前缀函数(或称 next 数组)。前缀函数 next[i] 表示模式串中前 i+1 个字符(即 s[0..i])的最长相等前后缀的长度。
-
通常有两种定义方式:
next[0] = -1:此时next[i]表示前i个字符(s[0..i-1])的最长相等前后缀长度减 1。例如模式串"abab",其next数组为[-1, -1, 0, 1](对应前缀长度分别为 0,0,1,2 再减 1)。next[0] = 0:此时next[i]直接表示前i个字符的最长相等前后缀长度。例如"abab"的next数组为[0, 0, 1, 2]。
本解法采用第一种(next[0] = -1)。构建 next 数组的经典过程如下(以模式串 s 自身为对象):
-
初始化
r = 0(当前处理位置),f = -1(回溯索引),next[0] = -1。 -
当
r < len - 1时:- 若
f == -1或s[r] == s[f],则f++,r++,next[r] = f; - 否则
f = next[f](回退)。
- 若
利用 KMP 判断重复子串:
若字符串 s 由某个子串重复多次构成,则其最长相等前后缀(不包含整个字符串本身)必然满足:len - (next[len-1] + 1) 是字符串长度的因子,且该差值即为最小重复子串的长度。原因如下:
- 设
next[len-1] + 1 = L,表示字符串s的最长相等前后缀长度为L。 - 若
s是重复串,则最长前后缀长度L一定等于len - d,其中d为最小重复子串的长度。 - 因此
len - L = d,且len % d == 0。 - 同时要排除
L == len的情况(即整个字符串与自身相等,这种情况仅在len=0时出现,本题非空)。
本解法中,re = next[len-1] + 1 即为最长相等前后缀长度。然后检查 len % (len - re) == 0 且 re != len(代码中通过 s[len-1]!=s[re-1] 间接排除了某些情况,但核心条件仍是取模)。若满足,则返回 true,否则 false。
优化后的 next 数组: 上述构建的 next 数组(next[0] = -1)在匹配失败时能快速回退,但我们在本题中直接使用其最后一个值来计算最长相等前后缀。注意,这里并不是优化后的 KMP(如 nextval),而是经典 next 数组。优化后的 next 数组(即 nextval)会进一步压缩回退路径,但对于本题目的判断逻辑并不改变。
4. 编写代码
解法一:移动匹配
c 运行
c
bool repeatedSubstringPattern(char* s) {
int len = strlen(s);
if (len == 1) return false;
char* t = (char*)malloc((2 * len + 1) * sizeof(char));
strcpy(t, s);
strcat(t, s);
char* p = t + 1;
char* end = t + 2 * len - 1;
*end = '\0';
bool found = (strstr(p, s) != NULL);
free(t);
return found;
}
解法二:KMP 算法
c 运行
c
bool repeatedSubstringPattern(char* s) {
int len = strlen(s);
if (len == 1) return false;
int* next = (int*)malloc(len * sizeof(int));
int r = 0, f = -1;
next[r] = f;
while (r < len - 1) {
if (f == -1 || s[r] == s[f]) {
f++;
r++;
next[r] = f;
} else {
f = next[f];
}
}
int re = next[len - 1] + 1;
if (len % (len - re) == 0 && re != len)
return true;
else
return false;
}