【问题标题】:Successive adding of char to get the longest word in the dictionary [closed]连续添加char以获得字典中最长的单词[关闭]
【发布时间】:2011-02-01 19:24:12
【问题描述】:

给定一个单词字典和一个初始字符。通过连续添加一个字符来查找字典中可能最长的单词。在任何给定的情况下,该词都应该是字典中的有效词。

ex : a -> at -> cat -> cart -> chart ....

【问题讨论】:

  • 有趣的问题。到目前为止,您尝试过什么,您在哪里卡住了?
  • 定义“单词词典”。它是哈希表、trie 还是什么?如果它是一个 trie,一个简单的 DF 搜索就可以了。它是您的标签建议的后缀树吗?
  • 听上去像是一道作业题,指定的不好。
  • 我相信这实际上是一个亚马逊面试问题。
  • 我看不出这个问题有什么问题。

标签: algorithm data-structures string suffix-tree


【解决方案1】:

蛮力方法是尝试使用深度优先搜索向每个可用索引添加字母。

因此,从“a”开始,您可以在两个位置添加新字母。在“a”的前面或后面,用下面的点表示。

.a.

如果添加“t”,则现在有三个位置。

.a.t.

您可以尝试将所有 26 个字母添加到每个可用位置。在这种情况下,字典可以是一个简单的哈希表。如果你在中间添加一个“z”,你会得到一个不在哈希表中的“azt”,所以你不会在搜索中继续沿着这条路径。

编辑:Nick Johnson 的图表让我很好奇所有最大路径的图表是什么样的。这是一张大图(1.6 MB):

http://www.michaelfogleman.com/static/images/word_graph.png

编辑:这是一个 Python 实现。蛮力方法实际上在合理的时间内运行(几秒钟,取决于起始字母)。

import heapq

letters = 'abcdefghijklmnopqrstuvwxyz'

def search(words, word, path):
    path.append(word)
    yield tuple(path)
    for i in xrange(len(word)+1):
        before, after = word[:i], word[i:]
        for c in letters:
            new_word = '%s%s%s' % (before, c, after)
            if new_word in words:
                for new_path in search(words, new_word, path):
                    yield new_path
    path.pop()

def load(path):
    result = set()
    with open(path, 'r') as f:
        for line in f:
            word = line.lower().strip()
            result.add(word)
    return result

def find_top(paths, n):
    gen = ((len(x), x) for x in paths)
    return heapq.nlargest(n, gen)

if __name__ == '__main__':
    words = load('TWL06.txt')
    gen = search(words, 'b', [])
    top = find_top(gen, 10)
    for path in top:
        print path

当然,答案中会有很多联系。这将打印前 N 个结果,以最终单词的长度来衡量。

使用 TWL06 Scrabble 字典输出起始字母“a”。

(10, ('a', 'ta', 'tap', 'tape', 'taped', 'tamped', 'stamped', 'stampede', 'stampedes', 'stampeders'))
(10, ('a', 'ta', 'tap', 'tape', 'taped', 'tamped', 'stamped', 'stampede', 'stampeder', 'stampeders'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'strangle', 'strangles', 'stranglers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'strangle', 'strangler', 'stranglers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranges', 'strangles', 'stranglers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranges', 'strangers', 'stranglers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranges', 'strangers', 'estrangers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranges', 'estranges', 'estrangers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranger', 'strangler', 'stranglers'))
(10, ('a', 'ta', 'tan', 'tang', 'stang', 'strang', 'strange', 'stranger', 'strangers', 'stranglers'))

这里是每个起始字母的结果。当然,一个例外是单个起始字母不必在字典中。只是一些可以用它组成的两个字母的单词。

(10, ('a', 'ta', 'tap', 'tape', 'taped', 'tamped', 'stamped', 'stampede', 'stampedes', 'stampeders'))
(9, ('b', 'bo', 'bos', 'bods', 'bodes', 'bodies', 'boodies', 'bloodies', 'bloodiest'))
(1, ('c',))
(10, ('d', 'od', 'cod', 'coed', 'coped', 'comped', 'compted', 'competed', 'completed', 'complected'))
(10, ('e', 're', 'rue', 'ruse', 'ruses', 'rouses', 'arouses', 'carouses', 'carousels', 'carrousels'))
(9, ('f', 'fe', 'foe', 'fore', 'forge', 'forges', 'forgoes', 'forgoers', 'foregoers'))
(10, ('g', 'ag', 'tag', 'tang', 'stang', 'strang', 'strange', 'strangle', 'strangles', 'stranglers'))
(9, ('h', 'sh', 'she', 'shes', 'ashes', 'sashes', 'slashes', 'splashes', 'splashers'))
(11, ('i', 'pi', 'pin', 'ping', 'oping', 'coping', 'comping', 'compting', 'competing', 'completing', 'complecting'))
(7, ('j', 'jo', 'joy', 'joky', 'jokey', 'jockey', 'jockeys'))
(9, ('k', 'ki', 'kin', 'akin', 'takin', 'takins', 'takings', 'talkings', 'stalkings'))
(10, ('l', 'la', 'las', 'lass', 'lassi', 'lassis', 'lassies', 'glassies', 'glassines', 'glassiness'))
(10, ('m', 'ma', 'mas', 'mars', 'maras', 'madras', 'madrasa', 'madrassa', 'madrassas', 'madrassahs'))
(11, ('n', 'in', 'pin', 'ping', 'oping', 'coping', 'comping', 'compting', 'competing', 'completing', 'complecting'))
(10, ('o', 'os', 'ose', 'rose', 'rouse', 'rouses', 'arouses', 'carouses', 'carousels', 'carrousels'))
(11, ('p', 'pi', 'pin', 'ping', 'oping', 'coping', 'comping', 'compting', 'competing', 'completing', 'complecting'))
(3, ('q', 'qi', 'qis'))
(10, ('r', 're', 'rue', 'ruse', 'ruses', 'rouses', 'arouses', 'carouses', 'carousels', 'carrousels'))
(10, ('s', 'us', 'use', 'uses', 'ruses', 'rouses', 'arouses', 'carouses', 'carousels', 'carrousels'))
(10, ('t', 'ti', 'tin', 'ting', 'sting', 'sating', 'stating', 'estating', 'restating', 'restarting'))
(10, ('u', 'us', 'use', 'uses', 'ruses', 'rouses', 'arouses', 'carouses', 'carousels', 'carrousels'))
(1, ('v',))
(9, ('w', 'we', 'wae', 'wake', 'wakes', 'wackes', 'wackest', 'wackiest', 'whackiest'))
(8, ('x', 'ax', 'max', 'maxi', 'maxim', 'maxima', 'maximal', 'maximals'))
(8, ('y', 'ye', 'tye', 'stye', 'styed', 'stayed', 'strayed', 'estrayed'))
(8, ('z', 'za', 'zoa', 'zona', 'zonae', 'zonate', 'zonated', 'ozonated'))

【讨论】:

  • +1。总是用蛮力技术来解决这样的问题很酷。 Wat 将是上述解决方案的顺序
  • 不错的解决方案!不是最有效的,因为您必须测试26^n 长度为n-1 的解决方案的可能性——如果这个词不是很短,那么它比长度为n 的词的数量要少得多——但它肯定完成工作。
  • 有人不加评论就删了我。考虑到我有一个完整的工作解决方案,有点有趣。好样的!
  • 不是 26^n。搜索树中的大多数中间“节点”都会失败,从而显着修剪搜索空间。如果不是这样,这将行不通。
  • 图片链接已失效。
【解决方案2】:

假设您需要重复执行此操作(或者您希望 26 个字母中的每一个都得到答案),请向后执行:

  1. 加载字典,并按长度降序排序
  2. 在单词和 (extension, max_len) 元组之间建立一个最初为空的映射。
  3. 对于排序列表中的每个单词:
    1. 如果它已经在映射中,则检索最大 len。
    2. 如果不是,请将最大 len 设置为字长。
    3. 检查删除字符产生的每个单词。如果该单词不在映射中,或者我们的 max_len 超过了映射中已经存在的单词的 max_len,则使用当前单词和 max_len 更新映射

然后,要获取给定前缀的链,只需从该前缀开始并在字典中反复查找它及其扩展。

这是示例 Python 代码:

words = [x.strip().lower() for x in open('/usr/share/dict/words')]
words.sort(key=lambda x:len(x), reverse=True)
word_map = {}  # Maps words to (extension, max_len) tuples

for word in words:
  if word in word_map:
    max_len = word_map[word][1]
  else:
    max_len = len(word)
  for i in range(len(word)):
    new_word = word[:i] + word[i+1:]
    if new_word not in word_map or word_map[new_word][2] < max_len:
      word_map[new_word] = (word, max_len)

# Get a chain for each letter
for term in "abcdefghijklmnopqrstuvwxyz":
  chain = [term]
  while term in word_map:
    term = word_map[term][0]
    chain.append(term)
  print chain

以及每个字母的输出:

['a', 'ah', 'bah', 'bach', 'brach', 'branch', 'branchi', 'branchia', 'branchiae', 'branchiate', 'abranchiate']
['b', 'ba', 'bac', 'bach', 'brach', 'branch', 'branchi', 'branchia', 'branchiae', 'branchiate', 'abranchiate']
['c', 'ca', 'cap', 'camp', 'campo', 'campho', 'camphor', 'camphory', 'camphoryl', 'camphoroyl']
['d', 'ad', 'cad', 'card', 'carid', 'carida', 'caridea', 'acaridea', 'acaridean']
['e', 'er', 'ser', 'sere', 'secre', 'secret', 'secreto', 'secretor', 'secretory', 'asecretory']
['f', 'fo', 'fot', 'frot', 'front', 'afront', 'affront', 'affronte', 'affronted']
['g', 'og', 'log', 'logy', 'ology', 'oology', 'noology', 'nosology', 'nostology', 'gnostology']
['h', 'ah', 'bah', 'bach', 'brach', 'branch', 'branchi', 'branchia', 'branchiae', 'branchiate', 'abranchiate']
['i', 'ai', 'lai', 'lain', 'latin', 'lation', 'elation', 'delation', 'dealation', 'dealbation']
['j', 'ju', 'jug', 'juga', 'jugal', 'jugale']
['k', 'ak', 'sak', 'sake', 'stake', 'strake', 'straked', 'streaked']
['l', 'la', 'lai', 'lain', 'latin', 'lation', 'elation', 'delation', 'dealation', 'dealbation']
['m', 'am', 'cam', 'camp', 'campo', 'campho', 'camphor', 'camphory', 'camphoryl', 'camphoroyl']
['n', 'an', 'lan', 'lain', 'latin', 'lation', 'elation', 'delation', 'dealation', 'dealbation']
['o', 'lo', 'loy', 'logy', 'ology', 'oology', 'noology', 'nosology', 'nostology', 'gnostology']
['p', 'pi', 'pig', 'prig', 'sprig', 'spring', 'springy', 'springly', 'sparingly', 'sparringly']
['q']
['r', 'ra', 'rah', 'rach', 'brach', 'branch', 'branchi', 'branchia', 'branchiae', 'branchiate', 'abranchiate']
['s', 'si', 'sig', 'spig', 'sprig', 'spring', 'springy', 'springly', 'sparingly', 'sparringly']
['t', 'ut', 'gut', 'gutt', 'gutte', 'guttle', 'guttule', 'guttulae', 'guttulate', 'eguttulate']
['u', 'ut', 'gut', 'gutt', 'gutte', 'guttle', 'guttule', 'guttulae', 'guttulate', 'eguttulate']
['v', 'vu', 'vum', 'ovum']
['w', 'ow', 'low', 'alow', 'allow', 'hallow', 'shallow', 'shallowy', 'shallowly']
['x', 'ox', 'cox', 'coxa', 'coxal', 'coaxal', 'coaxial', 'conaxial']
['y', 'ly', 'loy', 'logy', 'ology', 'oology', 'noology', 'nosology', 'nostology', 'gnostology']
['z', 'za', 'zar', 'izar', 'izard', 'izzard', 'gizzard']

编辑:鉴于分支最终合并的程度,我认为绘制图表来证明这一点会很有趣:

这个挑战的一个有趣的延伸:一些字母可能有几个等长的词尾。哪一组链最小化最终节点的数量(例如,合并最多的字母)?

【讨论】:

  • 不错。比我的快 6-8 倍。但这只会为每个起始字母产生一条路径,而我的产生所有 380,000 条可能的路径(所有 26 个字母组合)。因此,最终这取决于 OP 需要算法来做什么。 (P.S. 'abranchiate' 不在我的字典里!)
  • 非常正确。您可以通过存储针对每个术语的扩展列表而不是迄今为止发现的最长路径来让它生成所有路径或仅生成所有最大长度路径。就字典而言,我只是在 OSX 10.5 上使用 /usr/share/dict/words 中的那个。 :)
  • +1 表示图表的想法。查看我的答案以获取所有最大路径的图表。 :)
【解决方案3】:

如果你想这样做一次,我会这样做(概括为以完整单词开头的问题):

拿出你的整个字典,扔掉没有目标词中字符超集的任何东西(假设它的长度为m)。然后将剩余的单词按长度分箱。对于长度为m+1 的每个单词,尝试删除每个字母,看看是否会产生您想要的单词。如果没有,扔掉它。然后检查每个长度为m+2 的单词与长度为m+1 的有效集合,丢弃任何不能减少的单词。继续前进,直到找到一个空集;你最后找到的东西是最长的。

如果您想快速查找,我会构建一个后缀树类似的数据结构。

按长度对所有单词进行分组。对于长度为 2 的每个单词,将其两个字符中的每一个放入“子词”集中,并将该词添加到每个字符的“超词”集中。现在您已经在长度为 2 的所有有效单词和所有字符之间建立了一个链接。对长度为 3 的单词和长度为 2 的有效单词执行相同操作。现在您可以从该层次结构中的任何位置开始,进行广度优先搜索以找到最深的分支。


编辑:这个解决方案的速度将很大程度上取决于语言的结构,但是如果我们决定使用具有log(n) 性能的集合构建所有操作(即我们使用红黑树等),我们有N(m) 长度为m 的单词,那么在长度为m+1m 的单词之间形成链接将大约为(m+1)*m*N(m+1)*log(N(m)) 时间(考虑到字符串比较在长度上花费线性时间细绳)。由于我们必须对所有字长执行此操作,因此构建完整数据结构的运行时间大约为

(typical word length)^3 * (dictionary length) * log (dictionary length / word length)

(初始分箱成一定长度的单词需要线性时间,因此可以忽略;实际的运行时公式很复杂,因为它取决于字长的分布;对于您从单个单词它甚至更复杂,因为它取决于具有较短子词的较长单词的预期数量。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-04
    • 2023-03-12
    • 2020-12-26
    • 1970-01-01
    • 2018-03-08
    • 2022-07-03
    • 1970-01-01
    • 2011-08-30
    相关资源
    最近更新 更多