问题描述
小M在研究字符串时发现了一个有趣的现象:某些字符串是由一个较短的子串反复拼接而成的。如果能够找到这个最短的子串,便可以很好地还原字符串的结构。你的任务是给定一个字符串,判断它是否是由某个子串反复拼接而成的。如果是,输出该最短的子串;否则,输出空字符串""。
例如:当输入字符串为 abababab 时,它可以由子串 ab 反复拼接而成,因此输出 ab;而如果输入 ab,则该字符串不能通过子串的重复拼接得到,因此输出空字符串。
问题分析
这个问题的核心是找出字符串中最短的重复结构。一个字符串可以被看作是由重复子串构成的,当且仅当它可以被分成若干相同的子串部分。例如:
"abababab"是由子串"ab"组成的;"abcabcabcabc"是由子串"abc"组成的;"abcd"并不含有任何循环子串,所以应返回空结果。
我们的目标是高效地找出这个最短的循环子串,同时考虑时间和空间复杂度。
方法一:基于子串重复的匹配检查法
第一个方法是通过检查所有可能的子串,直到字符串长度的一半,以此判断这些子串是否能通过重复构成原始字符串。之所以只需检查长度的一半,是因为一个循环子串的长度一定小于等于原字符串的一半。具体步骤如下:
-
遍历可能的子串长度:从 1 开始遍历,直到字符串长度的一半 (
n // 2 + 1)。对于每一个长度i,取前i个字符作为候选子串。 -
检查该子串是否可以构成原始字符串:如果字符串的总长度可以被
i整除,那么该子串就有可能通过重复构成原字符串。我们将该子串重复(n // i)次,并检查结果是否等于原字符串。 -
返回结果:如果找到一个符合条件的子串,则返回该子串作为结果;否则,返回空字符串。
代码实现
以下是该方法的 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)),主要是因为在比较过程中会产生新的字符串。
方法二:双指针法
第二种方法则利用双指针的技巧,通过动态调整来寻找可能的重复子串。这种方法的核心是通过双指针实现字符串的部分匹配,从而发现循环结构。
算法步骤
-
初始化指针:设置两个指针
i和j,其中i指向字符串开头,而j从第二个字符开始。 -
匹配字符:当
inp[i] == inp[j]时,两个指针同时向后移动。如果字符匹配,我们继续移动指针;如果字符不匹配,我们将i重新置为 0,并将j向后移动一个位置,尝试新的对齐方式。 -
判断最短循环子串:当匹配过程结束后,我们可以通过子串
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 ""
代码详解
- 指针对齐:通过调整
i和j指针,来对齐字符串中可能的重复部分。 - 候选验证:在匹配结束后,通过截取
inp[:n - i]作为候选子串,验证其是否能通过重复构成原字符串。 - 效率:该方法避免了对每种长度的子串进行重复检查,因此在某些情况下比方法一更高效。
复杂度分析
- 时间复杂度:最优情况下接近 (O(n)),因为只需遍历字符串一次。
- 空间复杂度:(O(1)),仅使用了少量指针变量。
方法对比
这两种方法各有优劣,主要体现在复杂度和适用场景的不同:
-
基于子串重复的匹配检查法:该方法逻辑简单,易于理解。然而,当字符串较长时,它的时间复杂度会增加。
-
双指针法:这种方法更为高效,在某些情况下可以实现线性时间复杂度。但它实现起来稍微复杂一些。