日常刷题-还原原始字符串 | 豆包MarsCode AI刷题

115 阅读6分钟

问题描述

给定一个字符串 FF,这个字符串是通过对某个初始字符串 SS 执行若干次以下操作得到的:

  • 选择一个整数 KK(其中 0≤K<∣S∣0≤K<∣S∣,∣S∣∣S∣ 表示字符串 SS 的长度)
  • 将 SS 从第 KK 个位置(从0开始计数)到末尾的子串追加到 SS 的末尾,即:S=S+S[K:]S=S+S[K:]

输入格式

  • 输入为一个字符串 FF,仅包含小写字母,长度不超过 1000。

输出格式

  • 输出一个字符串,表示可能的最短初始字符串 SS。
  • 如果无法通过题目描述的操作得到字符串 FF,则输出原字符串 FF。

测试样例

样例1:

输入:str1 = "abbabbbabb"
输出:"ab"

解释:初始字符串 "ab" 可以通过以下步骤得到最终字符串:

  1. K=1K=1:"a[b]" → "a[b][b]"
  2. K=0K=0:"[abb]" → "[abb][abb]"
  3. K=2K=2:"ab[babb]" → "ab[babb][babb]"

样例2:

输入:str1 = "abbbabbbb"
输出:"ab"

解释:初始字符串 "ab" 可以通过以下步骤得到最终字符串:
"a[b]" → "a[b][b]"
"ab[b]" → "ab[b][b]"
"[abbb]" → "[abbb][abbb]"
"abbbabb[b]" → "abbbabb[b][b]"

样例3:

输入:str1 = "jiabanbananananiabanbananananbananananiabanbananananbananananbananananbanananan"
输出:'jiaban'

样例4:

输入:str1 = "selectecttectelectecttectcttectselectecttectelectecttectcttectectelectecttectcttectectcttectectcttectectcttect"
输出:'select'

样例5:

输入:str1 = "discussssscussssiscussssscussssdiscussssscussssiscussssscussssiscussssscussss"
输出:'discus'

样例6:

输入:str1 = "lflsdjlskjflskjfl"
输出:'lflsdjlskjfl'

提示

  1. 考虑如何判断一个字符串是否可以通过题目描述的操作得到
  2. 可以尝试从短到长枚举可能的初始字符串
  3. 时间复杂度应不超过 O(n2)O(n2),其中 nn 为输入字符串的长度

解决过程和思考

这道题的核心在于找到一个初始字符串 SS,通过一系列的自我扩展操作可以生成给定的字符串 FF。扩展操作是:选择一个整数 KK(从0开始计数)并将 SS 的从第 KK 个位置到末尾的子串追加到 SS 的末尾。我们的任务是找到可能的最短初始字符串 SS 或者确认没有这样的 SS。

1. 枚举初始字符串长度

我们从输入字符串 FF 的长度为1开始,一直到 nn 枚举所有可能的初始字符串的长度。对于每一个可能的长度 s_{\text{len}}slen​,我们取这个长度的前缀作为可能的初始字符串。

2. BFS(广度优先搜索)策略

为了探索从初始字符串 SS 生成 FF 的所有可能路径,我们使用了广度优先搜索(BFS)策略。使用BFS的原因是它可以逐层扩展字符串,确保我们找到的第一个匹配方案是最短的初始字符串。我们使用队列来管理每个可能的扩展状态,队列中的每个元素是一个元组 (S_{\text{current}}, \text{pos})(Scurrent​,pos),其中 S_{\text{current}}Scurrent​ 是当前扩展的字符串,\text{pos}pos 是已经匹配到输入字符串 FF 的位置。

3. 检查匹配和扩展

在每个BFS的迭代中,我们从队列中取出一个状态 (S_{\text{current}}, \text{pos})(Scurrent​,pos),检查是否已经成功匹配到字符串末尾(\text{pos} = npos=n),如果是,则返回当前的初始字符串 SS。

对于每个可能的 KK 值(从0到 \text{len}(S_{\text{current}}) - 1len(Scurrent​)−1),我们尝试将后缀 S_{\text{current}}[K:]Scurrent​[K:] 追加到 S_{\text{current}}Scurrent​ 中,该后缀的长度是 \text{len}(S_{\text{current}}) - Klen(Scurrent​)−K。我们检查该后缀是否可以在当前位置 \text{pos}pos 正确匹配 FF 的后续部分。

4. 状态去重

为了避免重复处理相同的状态,我们维护一个集合 \text{visited}visited,来存储每个已经访问过的状态(字符串和位置的组合)。只有在状态未被访问过时,才将其添加到队列中继续处理。

5. 终止条件

如果我们在遍历完所有可能的初始字符串长度后,仍然没有找到一个成功的扩展路径,我们返回输入字符串 FF 本身,这表明无法通过任何初始字符串生成目标字符串。

注意事项

  • 匹配检查:在检查后缀匹配时,需要确保不会越界(即 \text{pos} + \text{len_suffix} \leq n)。同时,使用字符串的 startswith 方法来验证当前后缀是否与目标字符串从当前位置开始匹配。
  • BFS的正确性:BFS保证了我们找到的第一个成功路径对应的初始字符串是最短的,因为我们是逐层扩展的,最先达到末尾的路径即为最短路径。
  • 时间复杂度:在最坏的情况下,我们可能会遍历整个字符串的所有前缀长度,并为每个前缀尝试所有可能的 KK 值,因此复杂度接近 O(n^2)O(n2)。然而,由于BFS的剪枝(通过状态去重),实际运行时间通常比最坏情况要好。

复杂度分析

  • 时间复杂度:总体来说,时间复杂度主要受以下几部分影响:

    • 枚举初始字符串长度需要 O(n)O(n)。
    • 对于每个初始字符串长度,最坏情况下需要检查 O(n)O(n) 个位置和 O(n)O(n) 个 KK 值。
    • 总体上,复杂度为 O(n^3)O(n3) 在最坏情况下(没有有效剪枝时),但由于状态去重和BFS的层次扩展,实际复杂度通常为 O(n^2)O(n2) 级别。
  • 空间复杂度:主要由队列和已访问状态集合的大小决定。最坏情况下,可能需要存储 O(n^2)O(n2) 个状态(每个状态包含一个字符串和一个位置),因此空间复杂度为 O(n^2)O(n2)。

总结

这个问题通过模拟字符串的扩展过程,同时利用BFS来逐层探索可能的生成路径,可以有效地找到可能的最短初始字符串。关键在于利用状态去重和广度优先搜索,既保证了正确性,也提高了效率。通过仔细的匹配检查和状态管理,我们可以在复杂度可控的范围内解决这个问题。

Solution代码

def solution(str1):
    from collections import deque

    n = len(str1)

    # 尝试所有可能的初始字符串长度,从1到n
    for s_len in range(1, n + 1):
        initial_S = str1[:s_len]
        if not str1.startswith(initial_S):
            continue  # 不是前缀,跳过

        # 使用队列进行BFS,状态为 (当前S, 当前匹配的位置)
        queue = deque()
        queue.append((initial_S, s_len))

        # 使用集合记录已访问的状态,避免重复处理
        visited = set()
        visited.add((initial_S, s_len))

        while queue:
            S_current, pos = queue.popleft()

            # 如果已经匹配到字符串末尾,返回初始S
            if pos == n:
                return initial_S

            # 尝试所有可能的K值
            for K in range(len(S_current)):
                suffix = S_current[K:]
                len_suffix = len(suffix)

                # 检查是否越界
                if pos + len_suffix > n:
                    continue

                # 检查当前后缀是否与目标字符串匹配
                if str1.startswith(suffix, pos):
                    new_S = S_current + suffix
                    new_pos = pos + len_suffix

                    # 检查新状态是否已经访问过
                    state = (new_S, new_pos)
                    if state not in visited:
                        visited.add(state)
                        queue.append(state)

                        # 如果新位置达到字符串末尾,返回初始S
                        if new_pos == n:
                            return initial_S

    # 如果无法通过任何初始S生成目标字符串,返回原字符串
    return str1
    
    ```