LeetCode-Python-3076. 数组中的最短非公共子字符串(哈希表 + Trie Tree)

给你一个数组 arr ,数组中有 n 个 非空 字符串。

请你求出一个长度为 n 的字符串数组 answer ,满足:

  • answer[i] 是 arr[i] 最短 的子字符串,且它不是 arr 中其他任何字符串的子字符串。如果有多个这样的子字符串存在,answer[i] 应该是它们中字典序最小的一个。如果不存在这样的子字符串,answer[i] 为空字符串。

请你返回数组 answer 。

示例 1:

输入:arr = ["cab","ad","bad","c"]
输出:["ab","","ba",""]
解释:求解过程如下:
- 对于字符串 "cab" ,最短没有在其他字符串中出现过的子字符串是 "ca" 或者 "ab" ,我们选择字典序更小的子字符串,也就是 "ab" 。
- 对于字符串 "ad" ,不存在没有在其他字符串中出现过的子字符串。
- 对于字符串 "bad" ,最短没有在其他字符串中出现过的子字符串是 "ba" 。
- 对于字符串 "c" ,不存在没有在其他字符串中出现过的子字符串。

示例 2:

输入:arr = ["abc","bcd","abcd"]
输出:["","","abcd"]
解释:求解过程如下:
- 对于字符串 "abc" ,不存在没有在其他字符串中出现过的子字符串。
- 对于字符串 "bcd" ,不存在没有在其他字符串中出现过的子字符串。
- 对于字符串 "abcd" ,最短没有在其他字符串中出现过的子字符串是 "abcd" 。

提示:

  • n == arr.length
  • 2 <= n <= 100
  • 1 <= arr[i].length <= 20
  • arr[i] 只包含小写英文字母。

第一种思路:

暴力解法,对于输入里的每个字符串,找到它所有的子字符串,存在字典里。

我们一共建立两个字典

1. 字符串 到 它所有唯一的子串

2. 所有字符串的可能的子串 到 它的频率

接着按照题意,对于每个字符串,找到其出现频率等于1 的子字符串,且长度最短并且字典序最小。

时间复杂度:O(N * M^3)

空间复杂度:O(N * M^3)

class Solution:
    def shortestSubstrings(self, arr: List[str]) -> List[str]:
        self.substring2freq = defaultdict(int)
        self.s2substring = defaultdict(set)
        for s in arr:
            for substring in self.get_substring(s):
                self.substring2freq[substring] += 1
                self.s2substring[s].add(substring)
        
        res = []
        for s in arr:
            min_unique_substring = None
            for substring in self.s2substring[s]:
                if self.substring2freq[substring] == 1:
                    # print(substring)
                    if not min_unique_substring or len(min_unique_substring) > len(substring) or (len(min_unique_substring) == len(substring) and min_unique_substring > substring):
                        min_unique_substring = substring
            res.append(min_unique_substring if min_unique_substring else "")
        return res
    
    def get_substring(self, s):
        res = set()
        for i in range(len(s)):
            for j in range(i, len(s)):
                res.add(s[i:j + 1])
        # print(s, res)
        return res

第二种思路:

对于判断子串的问题,我们也可以利用 Trie Tree 这个数据结构,

  • Trie 树存储了 arr 中所有字符串的所有后缀。
  • Trie 树中的每个节点代表一个子串,该子串由从根节点到该节点路径上的字符组成。
  • 每个节点包含:
    • 一个字典(children),将字符映射到子节点。
    • 一个集合(indices),包含该子串出现的字符串索引。

如果我们把所有的子串都放进 Trie Tree 中,再用一个 indices 集合表示有多少个字符串能够拥有这个子串,那么我们就可以快速地知道这个子字符串是不是唯一。

时间复杂度:O(N * M^2)

空间复杂度:O(26^M) or O(N^2 * M^2)(可以优化到O(N* M^2)

O(26^M) 是理论上最多的节点数,第一层 1 个,第二层 26个,第三层 26 * 26,直到 第M + 1层

的个数是O(N* M^2),它们之和就是 26 ^(M + 1),即O(26^M)

O(N^2 * M^2) 是因为,一共有N * M^2 个节点,每个节点可能的 indices 最长为 N,所以是O(N^2 * M^2)。注意这里可以优化,因为 indices,可以被替换成一个 bit 数字,或者是直接用counter 记录有几个字符串拥有这个子串。

class TrieNode:
    def __init__(self):
        self.children = {} # char -> Node
        self.indices = set()

class Solution:
    def shortestSubstrings(self, arr: List[str]) -> List[str]:
        root = TrieNode()
        # 1. build the Trie Tree with all substrings
        for index, s in enumerate(arr):
            m = len(s)
            for i in range(m):
                node = root
                for j in range(i, m):
                    char = s[j]
                    # insert char to node
                    if char not in node.children:
                        node.children[char] = TrieNode()
                    node = node.children[char]
                    node.indices.add(index)
        
        # 2. search in the Trie Tree
        res = []
        for index, s in enumerate(arr):
            m = len(s)
            min_len = inf
            min_unique_subs = None
            for i in range(m):
                node = root
                subs = ""
                for j in range(i, m):
                    char = s[j]
                    subs += char
                    node = node.children[char]
                    if node.indices == {index}:
                        if min_unique_subs is None or len(subs) < min_len or (len(subs) == min_len and min_unique_subs > subs):
                            min_len = len(subs)
                            min_unique_subs = subs
            res.append(min_unique_subs if min_unique_subs else "")
        return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值