包含三个字符串的最短字符串

标签: 贪心 字符串 枚举

难度: Medium

给你三个字符串 a ,b 和 c , 你的任务是找到长度 最短 的字符串,且这三个字符串都是它的 子字符串 。

如果有多个这样的字符串,请你返回 字典序最小 的一个。

请你返回满足题目要求的字符串。

注意:

  • 两个长度相同的字符串 a 和 b ,如果在第一个不相同的字符处,a 的字母在字母表中比 b 的字母 靠前 ,那么字符串 a 比字符串 b 字典序小 。
  • 子字符串 是一个字符串中一段连续的字符序列。

示例 1:

输入:a = "abc", b = "bca", c = "aaa"
输出:"aaabca"
解释:字符串 "aaabca" 包含所有三个字符串:a = ans[2...4] ,b = ans[3..5] ,c = ans[0..2] 。结果字符串的长度至少为 6 ,且"aaabca" 是字典序最小的一个。

示例 2:

输入:a = "ab", b = "ba", c = "aba"
输出:"aba"
解释:字符串 "aba" 包含所有三个字符串:a = ans[0..1] ,b = ans[1..2] ,c = ans[0..2] 。由于 c 的长度为 3 ,结果字符串的长度至少为 3 。"aba" 是字典序最小的一个。

提示:

  • 1 <= a.length, b.length, c.length <= 100
  • a ,b ,c 只包含小写英文字母。

Submission

运行时间: 139 ms

内存: 16.0 MB

class Solution:
    def minimumString(self, a: str, b: str, c: str) -> str:
        def merge(s: str, t: str) -> str:
            # 先特判完全包含的情况
            if t in s: return s
            if s in t: return t
            for i in range(min(len(s), len(t)), 0, -1):
                # 枚举:s 的后 i 个字母和 t 的前 i 个字母是一样的
                if s[-i:] == t[:i]:
                    return s + t[i:]
            return s + t

        ans = ""
        for a, b, c in permutations((a, b, c)):
            s = merge(merge(a, b), c)
            if ans == "" or len(s) < len(ans) or len(s) == len(ans) and s < ans:
                ans = s
        return ans

Explain

此题解采用的方法是首先定义一个合并两个字符串的函数,该函数通过检查字符串的前缀和后缀是否相匹配来找到最短的合并方式。如果一个字符串是另一个的子串,则直接返回包含关系中的较大字符串。如果不是,函数尝试寻找最大的公共前后缀,并在此基础上合并两个字符串。主函数中,通过考虑所有字符串的排列组合,利用这个合并函数逐步合并三个字符串,保证每次合并都尝试得到最短的可能字符串。最终比较所有可能的合并结果,选择长度最短的,如果长度相同则选择字典序最小的字符串。

时间复杂度: O(n^2)

空间复杂度: O(n)

class Solution:
    def minimumString(self, a: str, b: str, c: str) -> str:
        def merge(s: str, t: str) -> str:
            # Special case where one string contains the other
            if t in s: return s
            if s in t: return t
            # Attempt to find the largest common suffix and prefix
            for i in range(min(len(s), len(t)), 0, -1):
                if s[-i:] == t[:i]:
                    # Merge by overlapping the common part
                    return s + t[i:]
            # If no overlap, concatenate directly
            return s + t

        ans = ""
        # Check all permutations of the strings
        for a, b, c in permutations((a, b, c)):
            # Merge strings in the order of the current permutation
            s = merge(merge(a, b), c)
            # Update result if the new string is shorter or lexicographically smaller
            if ans == "" or len(s) < len(ans) or len(s) == len(ans) and s < ans:
                ans = s
        return ans

Explore

在合并函数中,前缀和后缀匹配的原理基于最大公共子序列的概念。如果两个字符串的结尾和开头部分重叠,那么可以通过重叠部分来合并,从而避免重复和减少总长度。例如,如果字符串 'abc' 和 'bcd' 合并,因为 'abc' 的 'bc' 和 'bcd' 的 'bc' 重叠,合并结果为 'abcd',比简单拼接 'abcbcd' 短。这种方法有效地减少了合并后的字符串长度,对于减少存储空间和提高处理效率非常有帮助。

如果合并函数中没有找到公共的前缀或后缀,直接拼接两个字符串是一种保守而简单的合并策略。然而,这种方法可能错过更优的合并结果,特别是在涉及到多个字符串合并时。例如,对于字符串 'abc', 'cde', 'efgh',如果先合并 'abc' 和 'cde',未发现重叠,则结果为 'abccde'。但若先考虑 'cde' 和 'efgh',因重叠得到 'cdefgh',再与 'abc' 合并时可得到 'abcdefgh'。因此,这种方法可能不总是产生最短的合并结果。

尽管考虑所有字符串的排列并合并可以在某些情况下找到最短的合并字符串,但这种方法在效率上并不总是最优的,特别是当字符串数量增加时。对字符串进行全排列会导致计算量急剧增加,因为排列的数量是字符串数量的阶乘,这在实际应用中可能导致效率问题。此外,这种方法可能在某些特殊的字符串组合情况下,未必能得到真正的最短结果,尤其是涉及到多重重叠或特殊顺序的情况。

在选择最短字符串结果时,如果遇到多个合并结果长度相同,题解中采用的策略是选择字典序最小的字符串。具体比较逻辑是在更新结果字符串时,不仅比较字符串长度,还要比较字典序。如果新的字符串长度小于当前最短字符串,或者长度相同但字典序更小,则更新结果字符串。这样可以确保最终结果不仅长度最短,而且在多个最短结果中字典序最小。