【问题标题】:Find strings which are prefixes of other strings查找作为其他字符串前缀的字符串
【发布时间】:2012-07-09 14:29:31
【问题描述】:

这是一道面试题。给定多个字符串,找到这样的字符串,它们是其他字符串的前缀。例如,给定strings = {"a", "aa", "ab", abb"},结果为{"a", "ab"}

最简单的解决方案是对字符串进行排序并检查每对两个后续字符串是否第一个是第二个的前缀。算法的运行时间就是排序的运行时间。

我猜还有另一种解决方案,它使用trie,并具有复杂性O(N),其中N 是字符串的数量。你能推荐一个这样的算法吗?

【问题讨论】:

  • 恐怕排序解决方案不会给你这样的运行时间。假设你有 {"a", "aa", "aaa"} - 你可以在 O(nlog(n)) 中对它们进行排序,但你仍然需要检查 "a" 是否是 "aa" 的前缀,以及 " a" 是 "aaa" 的前缀,而 "aa" 是 "aaa" 的前缀 - 这给了你 O(n^2)
  • @Michael 我提供了一种算法,我相信它可以在 O(N) 中解决您的问题,但您从未评论过它。您是否发现我的解决方案是正确的?如果是,请标记为正确答案,如果不是,我想听听您的意见

标签: algorithm data-structures language-agnostic trie


【解决方案1】:

关于 Trie,复杂度 O(N),我有以下想法: 你从空的 Trie 开始。 你一个一个接一个单词,然后把单词加到 Trie 上。 在向 Trie 添加一个词(我们称之为词 Wi)后,有两种情况需要考虑:

  1. Wi 是您之前添加的一些单词的前缀。 如果您在添加单词 Wi 时没有向 Trie 添加任何节点,则该陈述是正确的。 在这种情况下,Wi 是前缀,也是我们解决方案的一部分。
  2. 之前添加的一些单词是Wi的前缀。 如果您通过表示之前添加的某个单词的结尾的节点(让我们调用该单词 Wj),则该语句是正确的。在这种情况下,Wj 是 Wi 的前缀,也是我们解决方案的一部分。

更多细节(伪代码):

for word in words
    add word to trie
    if size of trie did not change then   // first case
        add word to result
    if ending nodes found while adding word   // second case
        add words defined by those nodes to result
return result

向 Trie 添加新词:

node = trie.root();
for letter in word
    if node.hasChild(letter) == false then   // if letter doesnt exist, add it
        node.addChild(letter)
    if letter is last_letter_of_word then   // if last letter of word, store that info
        node.setIsLastLetterOf(word)
    node = node.getChild(letter)    // move

在添加新单词时,您还可以检查是否通过了代表其他单词最后一个字母的任何节点。 我描述的算法复杂度是 O(N)。 另一个重要的事情是,这样你可以知道单词 Wi 前缀其他单词的次数,这可能很有用。

{aab, aaba, aa} 的示例: 绿色节点是检测为案例 1 的节点。 红色节点是检测为案例 2 的节点。 每列(trie)都是一个步骤。在开始的时候尝试是空的。 黑色箭头显示我们在该步骤中访问(添加)了哪些节点。 代表某个单词最后一个字母的节点将该单词写在括号中。

  1. 在第 1 步中,我们添加单词 aab。
  2. 在第 2 步中,我们添加单词 aaba,识别一个案例 2(单词 aab)并将单词 aab 添加到结果中。
  3. 在第 3 步中,我们添加单词 aa,识别案例 1 并将单词 aa 添加到结果中。

最后我们得到了正确的结果 = {aab, aa}。

【讨论】:

    【解决方案2】:

    原始答案是正确的:是b子字符串 的字符串a(误读)。

    使用 trie,您可以在第一次迭代中简单地将所有字符串添加到其中,并在第二次迭代中开始读取每个单词,让它成为 w。如果你找到一个你读完的单词,但没有到达字符串终止符(通常是$),你会到达特里树中的某个节点v
    通过从v 执行DFS,您可以获得所有以w 为前缀的字符串。

    高级伪代码:

    t <- new trie
    for each word w:
       t.add(w)
    for each word w:
      node <- t.getLastNode(w)
      if node.val != $
         collection<- DFS(node) (excluding w itself)
         w is a prefix of each word in collection
    

    注意:为了优化它,你可能需要做一些额外的工作:如果ab的前缀,bc的前缀,那么a是@的前缀987654336@,所以 - 当你执行 DFS 时,如果你到达某个已经搜索过的节点 - 只需将其字符串附加到当前前缀。
    不过,由于可能存在二次次数的可能性 ("a", "aa", "aaa", ....),因此获得所有可能性需要二次时间。


    原始答案:查找a 是否是b子字符串

    建议的解决方案以二次复杂度运行,您需要检查每两对,给您O(n* (n-1) * |S|)

    您可以在第一次迭代中从字符串构建suffix tree,并在第二次迭代中检查每个字符串是否是另一个字符串的重要条目(不是其本身)。
    这个解决方案是O(n*|S|)

    【讨论】:

    • 我猜基数树也可以在这里使用相同数量的操作。
    • 就像 Archeg 所说的,可以使用 Radix Tree 来节省 Trie 中的空间。
    猜你喜欢
    • 2018-12-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 2012-08-31
    • 2013-07-16
    • 1970-01-01
    相关资源
    最近更新 更多