【问题标题】:Fastest algorithm for finding a word on a word search grid在单词搜索网格上查找单词的最快算法
【发布时间】:2014-11-07 04:01:57
【问题描述】:

我接受了软件开发人员职位的面试。那是电话面试。被问到这个问题,它一直困扰着我一整天

面试官让我想出一个在单词搜索网格上查找单词的通用方法。为简单起见,无需担心内存限制或在网格上对角搜索(从左到右和从上到下)。

我能想到的最好办法是在网格程序启动时创建一个哈希映射(在每次调用单词搜索之前)...让它创建一个 character = 的哈希映射> 行、列索引。这样您就可以在 O(1) 时间内执行初始扫描。然后从那里基本上从左到右或从上到下扫描。

我从他那里得到的印象是有更好的解决方案,但我还没有。解决此类问题的最快算法是什么?

【问题讨论】:

  • 当我们说“最快”时,我们是否在计算预计算时间?如果没有,那么我们可以预先计算所有内容,所有查询都将是 O(1)。
  • 如果你的问题是“请记住面试官想要什么”,恐怕这是不可能的。如果你想知道他们的意思,那么也许跟进提问的人。对此有大量的解决方案,选择“最佳”需要比此处提供的更多详细信息。

标签: algorithm


【解决方案1】:

如果内存不是问题并且我可以预处理数据,那么我会:

  1. 按行优先顺序制作网格的字符串表示形式。这是用于水平搜索。
  2. 按列优先顺序制作网格的字符串表示形式,用于垂直搜索。

当给出要搜索的词时,我会使用标准搜索算法(KMP、Boyer-Moore 等)来:

  1. 在行主要字符串中搜索单词。
  2. 反转单词并在行主要字符串中搜索。
  3. 在列主字符串中搜索单词。
  4. 反转单词并在列主字符串中搜索。

这在简单性、内存使用和速度之间取得了很好的平衡。事实上,它非常简单,因为您实际上并不需要实现搜索算法。只需使用运行时库提供的任何内容。

当然,您可以轻松修改标准搜索算法,将二维网格视为一维字符串,而无需提前实际进行转换。这比预处理更复杂,搜索速度也会稍慢,但需要的内存更少。

通过一次扫描就地完成这项工作会变得很复杂。但是您可以在一次扫描中轻松完成水平搜索(即从左到右和从右到左)。以及一次扫描中的垂直搜索。您只需一次搜索两个不同的字符串:单词和单词的反转版本。

【讨论】:

  • 这是一种可怕的方法。找到每个单词的时间是 O(4n),其中 n 是拼图中的字符数。如果添加对角线,您的算法会膨胀到 O(8n)。这两种估计都忽略了这样一个事实,即您的算法会找到不在拼图中的单词。对于那些寻求快速解决方案的人,只要知道这个问题可以在 O(n * m) 时间内解决,其中 n 是第一个字符出现,m 是所有具有 2 个或更多字符的单词的最后一个字符出现的次数。所有单字母单词都可以在 O(1) 时间内找到。
  • @deAtog:我前面说过这不是绝对最快的方法,但它是线性的。您的 O(n*m) 可能比 O(4n) 差得多。根据网格的大小和您要执行的搜索次数,实施修改后的 Aho-Corasick 特里和搜索可能会优于我们所述的任何一种方法。此外,您必须解释我的方法如何导致找到不存在的单词。
  • 由于您要连接列/行,因此您的方法会找到从一列/行到下一列/行的单词。需要额外的工作来确保找到的单词不会跨越行/列边界。我的方法永远不会比你的方法差,因为 n * m 组合中只有一小部分需要评估,绝大多数可以跳过。那些只做一个方向的人需要考虑,这个词要么匹配要么不匹配。
  • 我想我已经给出了很多关于如何实现这一点的提示。如果你能在 O(1) 中找到单个字母的单词,在 O(m) 中找到单词的第一个字符,在 O(n) 中找到单词的最后一个字符,那么唯一稍微困难的问题是如何确定字母是否从第一个字符到最后一个字符都是正确的,其中有 m*n 个组合要评估。大多数组合将是无效的,并且可以在 O(1) 时间内被证明是无效的。其他的最多可以在 O(w) 时间内证明有效或无效,其中 w 是单词的长度。
  • @deAtog,在您的 O(n*m) 解决方案中,m 可能大于 4,因此它实际上比找到第一个字母后仅搜索 4 个方向要慢。
【解决方案2】:

如果预处理数据不计入时间,那么您可以准备一个包含每个字母位置的向量数组。所以给定第一个字母,你直接去它出现的位置,然后检查 4(或 8)个方向的其余字母。

在另一个答案的 cmets 中,@deAtog 似乎建议使用数组来查找第一个 和最后一个 字母的位置。但即使是中等大小的网格,每个字母也可能出现超过 4 次,因此只检查 4 个方向可能会更快。

您可以将数组的想法扩展到一个二字数组(2 个字母组合)。 digram 地图包含 digrams 的位置和方向现在给定一个单词的前 2 个字母,您可以直接找到这些字母的位置和方向。对于单字母单词,您只需检查所有以该字母开头的 digram。我认为这提供了大小和速度的良好组合。

如果您真的不关心空间,您可以将数组的想法一直延伸到创建位置和方向的一致性,例如,最流行的 50,000 个单词。现在,如果您获得了该列表中的一个单词,您可以在查找索引中的单词所需的时间内找到它。

但我认为一致性是矫枉过正的。将图表映射到位置/方向可能是速度和空间的一个很好的折衷方案。

最后,如果预处理确实很重要,并且您只寻找一个单词,那么您可以对蛮力方法应用一个技巧:在边界周围存储带有额外空格的网格。这些包含一个非字母。这样做意味着您永远不必检查数组边界。如果你跑出网格的边缘,那里的值将不会匹配一个单词中的任何字母,所以你会在那里停止检查。

【讨论】:

  • 直到第二段中间你的答案都是正确的。我还说过,您可以在 O(1) 时间内使两个列表之间的许多组合无效。一旦您确定了此处的 O(1) 算法,查找单词的性能接近 O(n*m) 时间。
【解决方案3】:

我会说他希望你推动澄清。如果您正在搜索单词,那么我同意您的方法。如果您正在搜索单个单词,那么线性搜索第一个字母,然后在每个方向上搜索单词的其余部分会更快。

【讨论】:

  • 这并不比 Jim 的方法好多少。这种方法的优点是您不会以允许您找到不在拼图中的单词的方式复制拼图,并且单词的第一个字符在 O(n) 时间而不是 O(4n) 时间内找到时间,如果包括对角线,则为 O(8n) 时间。如果我是面试官,我会认为这两个答案都是普通开发人员会想出的,你的解决方案只比 Jim 的解决方案好一点。
猜你喜欢
  • 2013-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-29
相关资源
最近更新 更多