我对此的想法是,假设您有“回文”一词。从该单词开始,并给出我学过的可能单词的词典(“我的词典”),您可以生成:
do
in
drone
mind
plan
line
role
lad
pad
drape
...
现在想象一下,给定计算机形式的字典(文本文件中的单词列表),我们将以编程方式进行此操作。问题是如何有效地查找可以从一个单词生成的单词,每个单词。因此,对于字典中的每个单词,我们都会找到可以从中生成的每个单词。
从给定单词生成单词的规则是,每个字母只能使用一次,但是可以选择任意顺序的字母。
我开始尝试解决此问题的幼稚方法是首先将所有单词加载到哈希表中,这是JavaScript中的对象。似乎将它们放进去可能会更好,但不确定。然后遍历哈希表中的每个单词。尝试为给定单词生成所有可能长度的字母的所有可能组合。我不知道如何数学计算这是多少组合,但似乎很多。因此,对于“回文”来说,它是10个字母,但似乎仅是10个字母的组合便是一堆组合,更不用说9、8、7、6 ...
因此想知道您如何更有效地进行此操作。我确定有人已经找到了解决此问题的技巧。
注意,这不是家庭作业或面试问题,我只是很好奇如何有效地做到这一点。
答案 0 :(得分:1)
反过来做会更简单吗?只需查看字典中的所有单词,看看该单词是否可以由目标单词生成。这样,我们要做的就是一个一个地减去一个字母,一旦发现一个字母丢失,我们就可以声明失败并继续下一个单词。如果我们先将目标词转换成一个袋(计数集),我们的考试将会非常快。
这是一个Swift的例子。我需要几个实用程序功能:
func wordToCountedSet(_ s:String) -> NSCountedSet {
return NSCountedSet(array: Array(s))
}
func canMake(_ s:String, from cs:NSCountedSet) -> Bool {
let cs = cs.copy() as! NSCountedSet
for letter in s {
if !cs.contains(letter) {
return false
}
cs.remove(letter)
}
return true
}
请考虑一下NSCountedSet的效率(有您的哈希值)以及canMake
经常失败的事实。
为了说明,假设我们的字典仅由“无人机”,“计划”,“行星”和“木琴”组成。我不会打扰字典。我只是证明我们得到了正确的答案:
let cs = wordToCountedSet("palindrome")
canMake("drone", from:cs) // true
canMake("plan", from:cs) // true
canMake("planet", from:cs) // false
canMake("xylophone", from:cs) // false
使用“行星”,直到到达最后一个“ t”,我们不会失败,但是使用“木琴”,我们的第一个字母将失败。其他人必须减去测试单词的每个字母才能成功,但是没有简单的方法可以解决这个问题。即使这样,成功所需的最长时间是字典单词中的字母数。而且这非常快,因为NSCountedSet是散列的。显然,我们可以添加一些快捷方式(例如,您不能使一个单词比原始单词更长),但这并不是重点。
答案 1 :(得分:1)
这可能是trie数据结构的不错的应用。这使您可以开始查看单词的排列,而如果没有机会将单词放入词典中,则可以提早退出。例如,以palindrome
开头的dp
的排列有很多,但是您不会考虑其中的任何一个,因为dp
在这方面是一个死胡同。
在浏览palindrome
时,您可能正在浏览mode
。您会将其添加到找到的单词列表中。尝试中的mode
中还有其他子级,但modep
,modei
等中没有子级。您可以在搜索中切断整个分支。您将继续在分支上继续,子分支上会出现诸如model
,modern
之类的单词。
使用python中的字典将单词列表转换为trie很容易:
trie = {}
with open('words.txt') as words:
for word in map(lambda w: w.strip(), words):
cur = trie
for l in word:
cur = cur.setdefault(l, {})
cur['word'] = True # defined if this node indicates a complete word
有了一个合理的词表,根字典可能会为字母表中的每个字母都有一个键。但是随着您下降,它会迅速变小。例如,在一个小的单词列表中查找trie['w']
时,将使用诸如['a', 'e', 'h', 'i', 'o', 'r']
之类的键来表示字典中以wa
,we
等开头的单词。除非您有一本不常用词的字典,否则trie['q']
可能只有一个键u
。
一旦构建了特里,您就可以反复考虑所涉及单词的排列,并在找到它们时添加单词。这将比查看所有排列的速度快得多,这是因为当Trie当前分支中没有更多的字母与键匹配时,您会提前退出分支。
考虑到上面建立的trie和3000个常用单词的单词列表,通过递归爬取trie可以快速找到100个单词。计算内部for循环运行的次数显示2670
具有3000个单词的字典-考虑到“回文”中字母的排列有360万个,这还算不错。使用/usr/share/dict/words
中更大的单词列表(大约有25万个单词)需要21543个循环。查找“被子”仅运行了100次。
这里有一些Python代码,用于遍历每个有效排列的特里。
def findWords(word, trie = trie, cur = '', words = []):
for i, letter in enumerate(word):
if letter in trie:
if 'word' in trie[letter]:
words.append(cur + letter)
findWords(word[:i] + word[i+1:], trie[letter], cur+letter, words )
return words
words = findWords("palindrome")
结果:
['palm',
'pale',
'pain',
'pair',
'pan',
'panel',
'plan',
...
'media',
'ear',
'earn',
'end',
'era']