【问题标题】:Check if word can be made out of given letters fast检查单词是否可以快速由给定的字母组成
【发布时间】:2013-02-10 03:11:50
【问题描述】:

我有一些字母和频率计数。而且我有一个很长的单词列表(比如 100 万个)。

假设我有A-1, B-1, D-1(“最多一个A,最多一个B,最多一个D”),那么我可以发"BAD",但不能发"RAD"

我能否知道在 对数 时间内可以用这些字母组成哪些单词,或者类似的时间,而不是遍历所有单词并查看单词中每个字母的计数?

这些词可以使用什么数据结构?试试吧?我不知道他们。如果我可以用它存储每个单词所需的字母,那也很棒。请帮忙!

【问题讨论】:

  • 对数是什么?您将不得不检查每个单词,因此显然您不会发现任何在单词数上是次线性的算法。
  • @ruakh 这取决于。如果您只需要设置一次单词列表但会尝试多次尝试,则可以通过进行预处理来加快查找速度。
  • 不过,在最坏的情况下,我给你每个字母的 100 个,并要求你找出所有可以从这些字母中组成的单词。在这种情况下,您必须说出每个单词。
  • @Patashu 是的,我的意思是做一些预处理。而且我必须多次查询这些存储的单词。
  • 您有一个包含 1000000 个单词的列表。我给你一袋字母,其中包含字母表中每个字母的 100 个。你可以用那一袋字母把你列表中的每一个单词都写出来。因此,您的算法必须输出该列表中的每个单词。你不能有一个在 O(log n) 时间内运行但产生 O(n) 输出的算法。

标签: algorithm data-structures dictionary


【解决方案1】:

这是数据结构的(字面)草图。

             [root]
         ----- | -----
       A1      A2     B1 ...
  ----/-    ---|---    -\----
 B1 C1 [a]  B1 B2 C1  C1 C2 D2 ...

这是一棵树,叶子节点是单词列表中的单词。叶节点上的单词完全由字母袋组成,字母袋包含从根到该节点的路径。非叶节点用字母和计数标记。一个节点的子节点要么是叶子(一个词),要么在字母表中严格地有一个字母。所以,要找到“猫”,你要走A1,C1,T1 的路径,而cat(和act)将是T1 的一个孩子。在每个节点,您遍历 count ≤ 您的输入计数的子节点(因此对于袋子 A3, C1, T2,您将遍历标记为 A1、A2、A3、C1、T1 或 T2 的任何节点)。

在最坏的情况下(每个单词都匹配),遍历需要 O(n) 时间,但平均而言需要更少的时间。对于一个小的输入包,它只会遍历几个节点。对于一个大的输入包,它会遍历很多节点,但它也会找到很多单词。

至多在单词列表中每个字母包含一个节点,因此它的大小最多与单词列表的长度成正比。

这是一种节省时间和空间的结构,可以相对轻松地计算和存储——它不会比您的单词列表占用更多空间,而且查询速度非常快。

【讨论】:

  • 这是一个很好的解决方案,因为您有正确数量的字母(就像我的一样)但由于您必须将 cat 存储在 许多 个不同的位置(因为它可以由从actaaaaaccccccccccttttxyzzy 的任何东西构建而成)。这就是我在评论中提到的空间成本。
  • 不,您只将它存储在一个位置:作为 A1-C1-T1 的子节点(在未压缩版本中为 A1-B0-C1-D0-...-S0-T1)。如果给定aaaaacccccccccctxyzzy 作为输入,您将遍历A1-C1-T1 作为算法的一部分(以及A1-C2-T1“acct”、X1-Y2-Z2“xyzzy”等)。 (这里的表示法假定正在使用零压缩,即X1 是根的直接子级)。
  • 好吧,那么它没有你想象的那么高效。是的,这取决于单词列表中的字符数,但是为了找到单词,您必须为每个字母组合遍历所述树一次。例如,act 将需要遍历 actaccaattact、@9876543444444444 的六个不同的组合act。对于大尺寸的包来说,这将变得笨拙。
  • 并不是说我的更好,因为它有同样的缺点:-)
  • 不。你总是按字母顺序遍历。因此,在 A1,您检查 C1 和 T1。在 A1-C1,您遍历 T1 并获得“CAT”。在 A1-T1,你得到“AT”。你回到根并遍历 C1,然后是 C1-T1。然后你回到根并检查 T1。
【解决方案2】:

如果您需要包含所有个字母的单词,我以前做过类似的事情(我的填字游戏作弊程序,我很惭愧)。

我拿了一个字典文件并对其进行了预处理,以便每一行都对字母进行排序,然后是单词本身,例如:

aaadkrrv:aardvark

然后,如果您有字母 ardvkraa,请对其进行排序,然后在冒号之前查找包含该字符串的行。我使用了grep,因为 O(n) 已经足够好,但是您可以轻松地将所有行放入平衡的二叉树中,从而为您提供 O(log n) 复杂度。

如果您要查找仅使用 一些 个字母的单词,那将无济于事,但不清楚这是否是您想要的。

【讨论】:

  • 是的,只使用部分字母的单词是可能的。
  • 在 Python 中,d = {''.join(sorted(w)):w for w in wordlist}; print d[''.join(sorted(s))] 将在 O(n) 预处理时间和 O(1) 查找时间内完成,不需要二叉树。 (但这并不能解决 OP 的问题)。
  • @Bruce,那么我认为您可能仅限于 O(n)。以空间换时间总是可能,但我怀疑这里的空间成本太高了。
  • @nneonneo,这取决于您使用的 n。如果没有一些花招,任何有排序的东西都不太可能是 O(n) :-)
  • OK,O(n log k) 其中 k 是单词的平均长度,n 是输入的长度(不是单词的数量)。但是,k 相对于 n 非常小。
【解决方案3】:

我不能说我可以 100% 从您的描述中掌握您提出的问题,但据我所知,您可以做到以下几点:

你索引你的单词列表。例如,“B1”是一个索引,它将包含一个条目列表,其中包含不超过一个字母 B,或者满足您正在解决的问题的要求。您还可以使用“复合”索引,例如“A1B1”。考虑到您可以负担索引的时间预算,您可以创建非常深的哈希值。如果您使用 26 个字母的字母表并想要对 4 个字母组合进行哈希处理,则只有 14,950 个索引,如果是 3 个字母,则只有 2,600 个。可以在列表的一次迭代中构建索引,因此它们的创建是线性的。一旦你过了这个阶段,你的大部分查找将是对数的。在我的示例中,您的 4 个字母单词查找将是一次提取。当然,对于较长的字母组合,您首先使用索引,然后进行迭代。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-28
    • 2019-12-17
    • 1970-01-01
    • 1970-01-01
    • 2020-02-07
    • 1970-01-01
    • 2016-02-23
    • 2013-03-09
    相关资源
    最近更新 更多