青训营刷题打卡(还原原始字符串) | 豆包MarsCode AI 刷题

166 阅读5分钟

还原原始字符串

问题描述

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

  • 选择一个整数 KK(其中 0K<S0≤K<|S|, S|S| 表示字符串 SS 的长度)
  • 将 SS 从第 KK 个位置(从00开始计数)到末尾的子串追加到 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(n^2),其中 nn 为输入字符串的长度

问题分析

最开始我想到的方法是用 KMP,因为该题目很容易让人想到这个经典的字符串匹配算法。由于每次都是S=S+S[K:]S=S+S[K:],如果我们从后往前看该变换后的字符串的话,显然是满足KMP思路的,只要找到next数组中能够等于其下标的二分之一即可。但我不知道如何具体去做,于是我询问AI,能否给我一点解题思路,但是AI的思路更为粗糙,他让我直接删除nxet数组中能够被字符串长度整除的部分。这显然不对,我就告诉AI,你的思路和代码连测试样例都无法通过,他又开始说车轱辘话,搞得我也很无语。但实际上这道题并非KMP算法,因为如果单纯使用KMP的话思路需要求出每次删除后的字符串的next数组,这显然回到了暴力枚举。想到暴力枚举,我不禁思考,这道题能否直接模拟呢?貌似是可以的,我们延用题目表述的思路,只要倒着一直删除不就好了。但是应该采取怎样的措施去删除元素呢?如样例2所示,我们可以这样删除:

当前删除的轮次当前的字符串可以删除的字串删除后的字符串
0abbbabbbbbabbbabbb
1abbbabbbabbbabbb
2abbbbabb
3abbbab
4abNoneab

当无法删除删除时,返回结果即可。 但是这样删除有一个潜在的问题,如样例1,如果我们仍然这样删除:

当前删除的轮次当前的字符串可以删除的字串删除后的字符串
0abbabbbabbbabbabbbab
1abbabbbabbbababbab
2abbabNoneabbab

经过该方法删除后,只剩abbab,虽然也满足构造要求,但题目要求最短的原始字符串,显然ab才是最短的,而这种方法构造出的abbab不满足最短。

进一步分析

不满足最短,我直接暴力找到所有删除的方法,按照dfsbfs一一枚举不就好了。我们每次得到一个当前字符串时,就考虑其能够删除成那种情况,然后将所有的情况放入一个队列或者栈中,再从队列或者栈中拿出可以得到的字符串后继续删除,一直到某个字符串无法执行删除操作就判断该字符串的长度,如果该字符串长度比当前答案小就更新答案即可。

def iter_(s):
    n = len(s)
    res = []
    for i in range(n-1, 0, -1):
        if 2 * i - n < 0: return res
        if s[2*i-n:i] == s[i:n]:
            res.append(s[:i])

def solution(str1):
    ans = str1
    q = [str1]
    while q is not None and len(q) > 0:
        oldS = q.pop()
        if len(oldS) < len(ans):
            ans = oldS
        if len(q) == 0: q = iter_(oldS)
        else: q = iter_(oldS) + q
    return ans

简单说明

这样可能有人有疑问,那如果遇到相同长度的如何处理呢?显然这种情况是不存在的,根据题目的构造方法,最终得到的字符串一定是初始字符串的一个前缀,如果长度一样的话,那就说明字符串是一样的。

总结

我认为豆包AI可能还需要继续加强,比如给出一个问题的时候,如果我把测试样例也给他的话,我认为最基本的,他给出的代码至少需要通过所有测试样例。否则不要给用户以肯定的语气说他解决了该问题。其次,该题目也给我一个启发,似乎所有的题目都有一个暴力求解的方法,我们没必要一开始就想一个最终的方案,得到一个朴素的方法,后续在上面优化才是解决一个题目最原始和有效的方式。