LeetCode 459. 重复的子字符串

2 阅读4分钟

LeetCode 459. 重复的子字符串

摘要

LeetCode 459 重复的子字符串题解,提供两种解法:移动匹配法(利用字符串拼接)和 KMP 算法法(利用前缀函数)。前者时间复杂度 O(n²),后者 O(n),空间复杂度均为 O(n)。

标签

#算法 #LeetCode #字符串 #KMP #题解

目录

  1. 题目描述

  2. 易错点

  3. 思路

    • 解法一:移动匹配
    • 解法二:KMP 算法
  4. 编写代码


1. 题目描述

给定一个非空的字符串 s,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

text

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

示例 2:

text

输入: s = "aba"
输出: false

示例 3:

text

输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。(或子串 "abcabc" 重复两次构成。)

提示:

  • 1 <= s.length <= 10^4
  • s 由小写英文字母组成

2. 易错点

  1. 字符串长度为 1 时,不可能由重复子串构成,直接返回 false
  2. 移动匹配法中,拼接后的字符串去掉首尾字符时需注意索引范围
  3. KMP 算法中 next 数组的构建需要正确理解前缀函数含义,避免越界
  4. 判断条件:若 len % (len - next[len-1] - 1) == 0 且 next[len-1] + 1 != len,则返回 true,否则 false
  5. 注意不同 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])的最长相等前后缀的长度。

  • 通常有两种定义方式:

    1. next[0] = -1:此时 next[i] 表示前 i 个字符(s[0..i-1])的最长相等前后缀长度减 1。例如模式串 "abab",其 next 数组为 [-1, -1, 0, 1](对应前缀长度分别为 0,0,1,2 再减 1)。
    2. 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;
}