”字符串最短循环子串“题解 | 豆包MarsCode AI刷题

161 阅读5分钟

问题描述

小M在研究字符串时发现了一个有趣的现象:某些字符串是由一个较短的子串反复拼接而成的。如果能够找到这个最短的子串,便可以很好地还原字符串的结构。你的任务是给定一个字符串,判断它是否是由某个子串反复拼接而成的。如果是,输出该最短的子串;否则,输出空字符串""

例如:当输入字符串为 abababab 时,它可以由子串 ab 反复拼接而成,因此输出 ab;而如果输入 ab,则该字符串不能通过子串的重复拼接得到,因此输出空字符串。

问题分析

这个问题的核心是找出字符串中最短的重复结构。一个字符串可以被看作是由重复子串构成的,当且仅当它可以被分成若干相同的子串部分。例如:

  • "abababab" 是由子串 "ab" 组成的;
  • "abcabcabcabc" 是由子串 "abc" 组成的;
  • "abcd" 并不含有任何循环子串,所以应返回空结果。

我们的目标是高效地找出这个最短的循环子串,同时考虑时间和空间复杂度。

方法一:基于子串重复的匹配检查法

第一个方法是通过检查所有可能的子串,直到字符串长度的一半,以此判断这些子串是否能通过重复构成原始字符串。之所以只需检查长度的一半,是因为一个循环子串的长度一定小于等于原字符串的一半。具体步骤如下:

  1. 遍历可能的子串长度:从 1 开始遍历,直到字符串长度的一半 (n // 2 + 1)。对于每一个长度 i,取前 i 个字符作为候选子串。

  2. 检查该子串是否可以构成原始字符串:如果字符串的总长度可以被 i 整除,那么该子串就有可能通过重复构成原字符串。我们将该子串重复 (n // i) 次,并检查结果是否等于原字符串。

  3. 返回结果:如果找到一个符合条件的子串,则返回该子串作为结果;否则,返回空字符串。

代码实现

以下是该方法的 Python 实现:

def solution(inp):
    n = len(inp)
    for i in range(1, n // 2 + 1):
        # 如果当前子串长度可以整除字符串总长度
        if n % i == 0:
            # 获取候选子串
            candidate = inp[:i]
            # 检查重复后的子串是否等于原字符串
            if candidate * (n // i) == inp:
                return candidate
    return ""

代码详解

  • 候选子串:我们将长度为 i 的子串作为候选,检查其是否能够构成原字符串。
  • 乘法检查:通过将候选子串乘以 (n // i) 次来模拟重复,判断其是否等于原字符串。
  • 效率:该方法最多检查到字符串长度的一半,因此其时间复杂度接近于 O(n^2),对于中等长度的字符串来说效率已经较高。

复杂度分析

  • 时间复杂度:最坏情况下为 (O(n^2)),因为需要对每种可能的子串进行重复和比较。
  • 空间复杂度:(O(n)),主要是因为在比较过程中会产生新的字符串。

方法二:双指针法

第二种方法则利用双指针的技巧,通过动态调整来寻找可能的重复子串。这种方法的核心是通过双指针实现字符串的部分匹配,从而发现循环结构。

算法步骤

  1. 初始化指针:设置两个指针 ij,其中 i 指向字符串开头,而 j 从第二个字符开始。

  2. 匹配字符:当 inp[i] == inp[j] 时,两个指针同时向后移动。如果字符匹配,我们继续移动指针;如果字符不匹配,我们将 i 重新置为 0,并将 j 向后移动一个位置,尝试新的对齐方式。

  3. 判断最短循环子串:当匹配过程结束后,我们可以通过子串 inp[:n - i] 获取候选子串,并检查该子串能否通过重复构成原字符串。如果可以则返回候选子串,否则返回空字符串。

代码实现

以下是双指针法的 Python 实现代码:

def solution(inp):
    n = len(inp)
    i, j = 0, 1
    
    while j < n:
        # 如果字符匹配,两个指针同时后移
        if inp[i] == inp[j]:
            i += 1
            j += 1
        else:
            # 重置 i 指针,j 指针向后移动
            i = 0
            j += 1

    # 获取候选子串
    candidate = inp[:n - i]
    # 检查候选子串是否可以通过重复构成原字符串
    if candidate * (n // len(candidate)) == inp:
        return candidate
    else:
        return ""

代码详解

  • 指针对齐:通过调整 ij 指针,来对齐字符串中可能的重复部分。
  • 候选验证:在匹配结束后,通过截取 inp[:n - i] 作为候选子串,验证其是否能通过重复构成原字符串。
  • 效率:该方法避免了对每种长度的子串进行重复检查,因此在某些情况下比方法一更高效。

复杂度分析

  • 时间复杂度:最优情况下接近 (O(n)),因为只需遍历字符串一次。
  • 空间复杂度:(O(1)),仅使用了少量指针变量。

方法对比

这两种方法各有优劣,主要体现在复杂度和适用场景的不同:

  1. 基于子串重复的匹配检查法:该方法逻辑简单,易于理解。然而,当字符串较长时,它的时间复杂度会增加。

  2. 双指针法:这种方法更为高效,在某些情况下可以实现线性时间复杂度。但它实现起来稍微复杂一些。