给你一个数组 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