安娜的第二个解决方案是这个解决方案的灵感来源。
首先,将所有单词加载到内存中,并根据单词长度将字典分成多个部分。
对于每个长度,制作一个指向单词的指针数组的 n 个副本。对每个数组进行排序,以便字符串在旋转一定数量的字母时按顺序显示。例如,假设 5 字母单词的原始列表是 [plane, apple, space, train, happy, stack, hacks]。那么你的五个指针数组将是:
rotated by 0 letters: [apple, hacks, happy, plane, space, stack, train]
rotated by 1 letter: [hacks, happy, plane, space, apple, train, stack]
rotated by 2 letters: [space, stack, train, plane, hacks, apple, happy]
rotated by 3 letters: [space, stack, train, hacks, apple, plane, happy]
rotated by 4 letters: [apple, plane, space, stack, train, hacks, happy]
(如果可以节省平台空间,您可以使用整数来标识单词,而不是指针。)
要搜索,只需询问您需要将 模式 旋转多少,以便问号出现在末尾。然后就可以在相应的列表中进行二分查找了。
如果您需要查找 ??ppy 的匹配项,则必须将其旋转 2 以生成 ppy??。因此,在旋转 2 个字母时查看按顺序排列的数组。快速二分搜索发现“happy”是唯一匹配项。
如果您需要找到 th??g 的匹配项,则必须将其旋转 4 以生成 gth??。因此,请查看数组 4,其中二进制搜索发现没有匹配项。
无论有多少问号,只要它们一起出现,这都有效。
需要空间除了字典本身:对于长度为 N 的单词,这需要空间用于(N 倍于长度为 N 的单词的数量)指针或整数。
每次查找的时间:O(log n),其中 n 是适当长度的单词数。
在 Python 中的实现:
import bisect
class Matcher:
def __init__(self, words):
# Sort the words into bins by length.
bins = []
for w in words:
while len(bins) <= len(w):
bins.append([])
bins[len(w)].append(w)
# Make n copies of each list, sorted by rotations.
for n in range(len(bins)):
bins[n] = [sorted(bins[n], key=lambda w: w[i:]+w[:i]) for i in range(n)]
self.bins = bins
def find(self, pattern):
bins = self.bins
if len(pattern) >= len(bins):
return []
# Figure out which array to search.
r = (pattern.rindex('?') + 1) % len(pattern)
rpat = (pattern[r:] + pattern[:r]).rstrip('?')
if '?' in rpat:
raise ValueError("non-adjacent wildcards in pattern: " + repr(pattern))
a = bins[len(pattern)][r]
# Binary-search the array.
class RotatedArray:
def __len__(self):
return len(a)
def __getitem__(self, i):
word = a[i]
return word[r:] + word[:r]
ra = RotatedArray()
start = bisect.bisect(ra, rpat)
stop = bisect.bisect(ra, rpat[:-1] + chr(ord(rpat[-1]) + 1))
# Return the matches.
return a[start:stop]
words = open('/usr/share/dict/words', 'r').read().split()
print "Building matcher..."
m = Matcher(words) # takes 1-2 seconds, for me
print "Done."
print m.find("st??k")
print m.find("ov???low")
在我的电脑上,系统字典有 909KB 大,这个程序除了存储单词(指针是 4 个字节)外,还使用了大约 3.2MB 的内存。对于这本词典,您可以使用 2 字节整数而不是指针将其减半,因为每个长度的单词少于 216 个。
测量结果:在我的机器上,m.find("st??k") 在 0.000032 秒内运行,m.find("ov???low") 在 0.000034 秒内运行,m.find("????????????????e") 在 0.000023 秒内运行。
通过写出二进制搜索而不是使用 class RotatedArray 和 bisect 库,我将前两个数字缩短到 0.000016 秒:速度是原来的两倍。在 C++ 中实现它会更快。