1. 引言

在本篇文章中,我们将介绍三种判断字符串周期性的方法。换句话说,我们要判断一个字符串是否可以由其某个前缀重复拼接而成。

这在实际开发中有一些应用场景,比如:判断一个模式是否重复出现、字符串压缩、密码学中某些模式识别等。

2. 问题描述

给定一个长度为 n 的字符串 s(n > 1),我们要判断是否存在一个长度为 m 的前缀 z = s[0..m-1](1 ≤ m < n),使得 s 可以表示为 z 重复拼接若干次的结果。

例如,字符串 abcabcabc 可以由前缀 abc 重复三次构成。

⚠️注意:必须是前缀,例如 bca 不是 abcabcabc 的前缀,所以不能用来构成整个字符串。

3. 暴力解法

思路

遍历所有可能的前缀长度 m(从 1 到 n-1),对每个前缀 z = s[0..m-1],尝试拼接它若干次是否能还原出 s

判断方式如下:

  • 对于每个位置 i,我们计算它在 z 中的对应位置 (i - 1) % m,然后比较字符是否相等。
  • 如果所有位置都匹配,则说明 s 是由 z 构成的。

示例代码(Java)

public class BruteForcePeriodChecker {
    public static String checkPeriodicity(String s) {
        int n = s.length();
        for (int m = 1; m < n; m++) {
            String z = s.substring(0, m);
            boolean valid = true;
            for (int i = 0; i < n; i++) {
                if (s.charAt(i) != z.charAt(i % m)) {
                    valid = false;
                    break;
                }
            }
            if (valid) return z;
        }
        return null;
    }
}

时间复杂度

  • 最坏情况下是 **O(n²)**。
  • 因为每个 m 都可能需要比较 n 次字符,总共最多尝试 n-1 次。

缺点

  • 没有提前剪枝,效率低。
  • 即使 m 不能整除 n,也照样尝试,这是不必要的。

4. 优化解法

改进点

  • 只尝试那些能整除 nm 值。
  • 这样可以减少不必要的尝试。

示例代码(Java)

public class OptimizedPeriodChecker {
    public static String checkPeriodicity(String s) {
        int n = s.length();
        for (int m = 1; m <= n / 2; m++) {
            if (n % m != 0) continue;
            String z = s.substring(0, m);
            boolean valid = true;
            for (int i = 0; i < n; i++) {
                if (s.charAt(i) != z.charAt(i % m)) {
                    valid = false;
                    break;
                }
            }
            if (valid) return z;
        }
        return null;
    }
}

时间复杂度分析

  • 最坏情况下,时间复杂度约为 **O(n log n)**。
  • 因为只尝试 d(n)m(即 n 的因数个数),而 d(n) 的增长速率远小于 n

优点

  • 相比暴力法有明显提升。
  • 适用于大多数实际场景。

5. 旋转法(最优解)

核心思想

利用字符串旋转定理:

一个字符串 s 是其某个子串的重复拼接,当且仅当它是自己的非平凡旋转字符串。

换句话说:

  • 如果 s 是某个前缀的重复拼接,那么将 s 拼接两次(s + s)后,s 应该出现在 s+s 的中间某个位置(不是开头或结尾)。

示例说明

  • s = abcabcabc,拼接后为 abcabcabcabcabcabc
  • 检查 s 是否出现在 s+s 的中间,即 s+s.indexOf(s, 1) 是否存在。

示例代码(Java)

public class RotationPeriodChecker {
    public static String checkPeriodicity(String s) {
        int n = s.length();
        String doubled = s + s;
        // 查找从位置1开始的 s 出现的位置
        int index = doubled.indexOf(s, 1);
        if (index != -1 && index < n) {
            return s.substring(0, index);
        }
        return null;
    }
}

时间复杂度

  • **O(n)**,因为 indexOf 在 Java 中使用的是高效的字符串匹配算法(如 KMP 或 Boyer-Moore)。
  • 是目前性能最优的解法。

优点

  • 线性时间复杂度。
  • 实现简洁优雅。
  • 是字符串处理中经典的技巧之一。

6. 总结

方法 时间复杂度 实现难度 适用场景
暴力法 O(n²) ✅简单 小数据量
优化法 O(n log n) ✅中等 中等数据量
旋转法 O(n) ✅中等偏上 大数据量、性能敏感场景

推荐优先使用旋转法,它不仅效率高,而且体现了字符串处理中的一个经典思想。

💡踩坑提醒:有些同学会尝试用正则表达式或动态规划来做,但容易写出 bug 或性能不佳的实现。旋转法是更优雅、更稳定的选择。


原始标题:How to Check String Periodicity