Algorithm 以最高分解决拼字游戏

Algorithm 以最高分解决拼字游戏,algorithm,dynamic-programming,string-matching,backtracking,trie,Algorithm,Dynamic Programming,String Matching,Backtracking,Trie,有人问我一个问题 你会得到一个字符列表,一个与每个字符相关的分数和一个有效单词词典(比如普通英语词典)。您必须从字符列表中生成一个单词,以便分数最大且单词有效 我可以想出一个解决方案,使用字典制作trie并使用可用字符进行回溯,但无法正确制定。有人知道正确的方法或想出了正确的方法吗?首先重复你的字母,并计算你在英语字母表中每个字符有多少次。将其存储在一个静态数组中,例如大小为26的char数组,其中第一个单元格对应于a第二个单元格对应于b,依此类推。将此原始数组命名为cnt。现在迭代所有单词,并

有人问我一个问题

你会得到一个字符列表,一个与每个字符相关的分数和一个有效单词词典(比如普通英语词典)。您必须从字符列表中生成一个单词,以便分数最大且单词有效


我可以想出一个解决方案,使用字典制作trie并使用可用字符进行回溯,但无法正确制定。有人知道正确的方法或想出了正确的方法吗?

首先重复你的字母,并计算你在英语字母表中每个字符有多少次。将其存储在一个静态数组中,例如大小为26的
char
数组,其中第一个单元格对应于
a
第二个单元格对应于
b
,依此类推。将此原始数组命名为cnt。现在迭代所有单词,并为每个单词形成一个大小为26的类似数组。对于此数组中的每个单元格,请检查在
cnt
中出现的次数是否至少相同。如果是这样的话,你可以写这个词,否则你就写不出来了。如果你能写出这个单词,你就可以计算它的分数,并在helper变量中最大化分数


这种方法将具有线性复杂度,这也是您可能具有的最佳渐进复杂度(在所有输入都是线性大小之后)。

这里是python中的暴力方法,使用包含58109个单词的英语词典。这种方法实际上非常快,每次运行大约0.3秒

from random import shuffle
from string import ascii_lowercase
import time

def getValue(word):
    return sum(map( lambda x: key[x], word))

if __name__ == '__main__':
    v = range(26)
    shuffle(v)
    key = dict(zip(list(ascii_lowercase), v))

    with open("/Users/james_gaddis/PycharmProjects/Unpack Sentance/hard/words.txt", 'r') as f:
        wordDict = f.read().splitlines()
        f.close()

    valued = map(lambda x: (getValue(x), x), wordDict)
    print max(valued)

这里是我使用的,为了方便起见,删除了一个连字符的条目。

构建一个查找trie,只包含字典中每个单词的已排序的字谜。这是一次性费用

我所说的排序字谜是指:如果单词是
eat
,则表示为
aet
。这个词是
tea
,你把它表示为
aet
bubble
表示为
bbbelu
等等

由于这是拼字游戏,假设您有8个磁贴(假设您想使用板上的一个磁贴),您将需要最多检查2^8个可能性

对于8组中的任何分片子集,可以对分片进行排序,并在anagram trie中进行查找

最多有2^8个这样的子集,通过更聪明的子集生成,这可能会得到优化(在重复瓷砖的情况下)

如果这是一个更一般的问题,其中2^{number of tiles}可能比字典中的字谜词总数高得多,那么最好像Ivaylo的答案那样使用频率计数,并且可以使用多维范围查询来优化查找。(在本例中为26个尺寸!)


很抱歉,这可能对您没有帮助(我想您正在尝试做一些练习并有一些限制),但我希望这将对没有这些限制的未来读者有所帮助。

我们可以假设字典是固定的,分数是固定的,只有可用的字母会改变(如拼字游戏)?否则,我认为没有比按照前面的建议查找字典中的每个单词更好的了

让我们假设我们在这个环境中。选择一个尊重信件成本的订单。例如Q>Z>J>X>K>。>A>E>I..>美国

将你的字典D替换为一个字典D',该字典由D的单词的字谜和按上一个顺序排列的字母组成(例如,buzz这个单词被映射到zzbu),如果你的游戏中最多有8个字母,还可以删除重复的单词和长度大于8的单词

然后用单词D'构造一个trie,其中子节点按字母值排序(因此根的第一个子节点是Q,第二个子节点是Z,…,最后一个子节点是U)。在trie的每个节点上,还存储通过该节点的单词的最大值


给定一组可用字符,您可以以深度优先的方式探索trie,从左到右,并将找到的当前最佳值保存在内存中。仅浏览节点值大于当前最佳值的分支。这样,您将只探索第一个分支之后的几个分支(例如,如果您在游戏中有一个Z,则探索以一个点字母开头的任何分支都将被放弃,因为它最多将获得8x1的分数,该分数小于Z的值)。我打赌你每次只会探索很少几个分支。

灵感来自程序员的答案(最初我认为这种方法是O(n!),所以我放弃了它)。它需要为每个问题设置O(字数),然后设置O(2^(查询中的字符))。这是指数级的,但在拼字游戏中一次只有7个字母块;所以你只需要检查128种可能性

第一个观察结果是,查询或word中字符的顺序无关紧要,因此您希望将列表处理为一组字符。一种方法是对单词进行“排序”,使“bac”、“cab”变成“abc”

现在,接受查询,并迭代所有可能的答案。保留/放弃每个字母的所有变体。以二进制形式更容易看到:1111保留全部,1110丢弃最后一个字母

然后检查字典中是否存在每种可能性(为简单起见,请使用哈希映射),然后返回得分最高的可能性

import nltk
from string import ascii_lowercase
from itertools import product

scores = {c:s for s, c in enumerate(ascii_lowercase)}
sanitize = lambda w: "".join(c for c in w.lower() if c in scores)
anagram = lambda w: "".join(sorted(w))

anagrams = {anagram(sanitize(w)):w for w in nltk.corpus.words.words()}

while True:
    query = input("What do you have?")
    if not query: break

    # make it look like our preprocessed word list
    query = anagram(sanitize(query))

    results = {}

    # all variants for our query
    for mask in product((True, False), repeat=len(query)):
        # get the variant given the mask
        masked = "".join(c for i, c in enumerate(query) if mask[i])
        # check if it's valid
        if masked in anagrams:
            # score it, also getting the word back would be nice
            results[sum(scores[c] for c in masked)] = anagrams[masked]

    print(*max(results.items()))

如果字典条目的数量相对较少(高达几百万),您可以使用蛮力:为每个单词创建一个32位掩码。预处理数据:如果使用字母a/b/c/../z,则设置一位。对于六个最常见的英文字符,如果字母使用两次,则设置另一位

为您拥有的字母创建一个类似的位图。然后,在位图中为可用字母设置单词所需的所有位时,扫描字典中的单词。您已将问题简化为一个单词,其中所有字符都需要一次,如果需要两次,则最常用的六个字符需要两次。你仍然需要检查一个词是否可以被使用