📜  门| GATE-CS-2015(套装2)|问题 25(1)

📅  最后修改于: 2023-12-03 14:58:30.431000             🧑  作者: Mango

题目 25

此题目要求实现一个算法,对于给定的一个字符串集合,找到所有的大于等于 $k$ 长度的子字符串,并统计它们的出现次数。字符串集合和 $k$ 均为函数参数。

def findSubstrings(stringList, k):
    # 返回一个字典,key 为子字符串,value 为出现次数
    pass
输入

函数的输入参数为一个字符串集合 stringList 和一个整数 k,其中

  • $1 \leq |stringList| \leq 10^3$
  • $1 \leq k \leq 10$

其中字符串集合中每个字符串的长度均小于等于 $100$。

输出

函数应该返回一个字典,其中 key 为大于等于 $k$ 长度的子字符串,value 为该子字符串在所有字符串中出现的次数。如果子字符串在不同的字符串中出现,则应该把出现次数累加起来。

示例
stringList = ["abababa", "aaa", "abc"]
k = 3
findSubstrings(stringList, k)

输出

{
    "aba": 2,
    "bab": 1,
    "abab": 1,
    "baba": 1,
    "aaa": 1
}
解题思路

考虑最暴力的方式,把所有大于等于 $k$ 长度的子字符串都枚举出来,在字典中进行计数。时间复杂度为 $O(n^3)$,其中 $n$ 是所有字符串的长度之和。显然,如果字符串集合中的最大长度为 $100$,则上述暴力算法的时间复杂度为 $10^9$,无法接受。

我们可以考虑用 字典树 来解决问题。将字符串集合中的每一个字符串都插入到字典树中,同时在每个节点上保存从根节点到该节点的路径所代表的字符串的出现次数。遍历字典树时,对于当前节点表示的字符串的出现次数进行累加,最终的累加和即为该字符串出现的次数。时间复杂度为 $O(nk)$,其中 $n$ 是字符串集合中所有字符串的长度之和,$k$ 是最长的字符串的长度。

参考代码
class TrieNode:
    def __init__(self):
        self.count = 0
        self.child = {}

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for ch in word:
            if ch not in node.child:
                node.child[ch] = TrieNode()
            node = node.child[ch]
            node.count += 1

def findSubstrings(stringList, k):
    trie = Trie()
    for s in stringList:
        length = len(s)
        for i in range(length - k + 1):
            trie.insert(s[i:i+k])
            
    res = {}
    q = [(trie.root, "")]
    while q:
        node, word = q.pop()
        if node.count > 0 and len(word) >= k:
            res[word] = node.count
        for ch in node.child:
            q.append((node.child[ch], word+ch))
    return res

这里的 Trie 类表示实现了一个字典树,其中 insert 方法可以用来把字符串插入到字典树中。 findSubstrings 函数中,首先将所有大于等于 k 长度的子字符串都插入到字典树中,然后从字典树的根节点开始遍历整棵字典树,记录每个节点表示的字符串的出现次数。最终的结果保存在一个字典中,字典的 key 为所有大于等于 $k$ 长度的子字符串,value 为子字符串在所有字符串中出现的次数。