我在这里有一个问题,我正在努力解决。
程序被赋予一个包含以下字符的文本文件:a - z,A - Z,0 - 9,fullstop(。)和空格。文本文件中的单词纯粹由a-z,A-Z和0-9组成。该程序收到几个查询。每个查询都由文件中已存在的一组完整单词组成。程序应返回所有单词存在的文件中的最小短语(以任何顺序)。如果有很多这样的短语,请返回第一个短语。
这是一个例子。我们假设该文件包含:
Bar is doing a computer science degree. Bar has a computer at home. Bar is now at home.
查询1:
Bar computer a
响应:
Bar has a computer
查询2:
Bar home
响应:
home. Bar
我想到了这个解决方案。对于查询1,首先搜索Bar,并将所有三个Bar组合为列表。列表中的每个节点还包含最小短语的起始位置和总长度。所以它看起来像
第一个节点“Bar,0,1”[查询,开始posn,总长度]。 类似地,对于第二和第三节点。
搜索下一台计算机。计算每次出现Bar的计算机的最小距离。
第一个节点“Bar Computer”,0,5
第二个节点“Bar Computer”,7,4等等,用于其他节点
搜索下一个“a”。搜索必须从每个节点提到的起始位置开始,并且必须左右移动直到找到该单词,因为顺序不重要。必须选择最小的出现次数。
这个解决方案是否正确?我觉得这样做,我必须警惕很多情况,可能有一个更简单的解决方案。
如果单词是唯一的,它会成为TSP的变种吗?
答案 0 :(得分:4)
TSP不是思考这个问题的好方法。设n是文本的长度,m是查询的长度;假设n>米天真的解决方案
best = infinity
for i = 1 to n
for j = i to n
all_found = true
for k = 1 to m
found = false
for l = i to j
if text[l] == query[k]
found = true
all_found = all_found || found
if all_found && j - i < best
best = j - i
best_i = i
best_j = j
对于有界长度的单词,已经是O(n 3 m)的多项式时间。现在让我们进行优化。
首先,通过哈希集提升内部循环。
best = infinity
for i = 1 to n
for j = i to n
subtext_set = {}
for l = i to j
subtext_set = subtext_set union {text[l]}
all_found = true
for k = 1 to m
all_found = all_found && query[k] in subtext_set
if all_found && j - i < best
best = j - i
best_i = i
best_j = j
如果我们使用二叉树,运行时间现在是O(n 3 )或O(n 3 log n)。
现在观察当上限增加1时重新计算subtext_set
是浪费的。
best = infinity
for i = 1 to n
subtext_set = {}
for j = i to n
subtext_set = subtext_set union {text[l]}
all_found = true
for k = 1 to m
all_found = all_found && query[k] in subtext_set
if all_found && j - i < best
best = j - i
best_i = i
best_j = j
我们在O(n 2 m)。现在,当subtext_set
仅由一个元素增加时,重新检查整个查询似乎很浪费:为什么我们不检查那个元素,还记得我们要去多少?
query_set = {}
for k = 1 to m
query_set = query_set union {query[k]}
best = infinity
for i = 1 to n
subtext_set = {}
num_found = 0
for j = i to n
if text[l] in query_set && text[l] not in subtext_set
subtext_set = subtext_set union {text[l]}
num_found += 1
if num_found == m && j - i < best
best = j - i
best_i = i
best_j = j
我们在O(n 2 )。获得O(n)需要一些见解。首先,让我们看看每个子字符串包含的示例
的查询字数text = Bar has a computer at home. Bar
1 2 3 4 5 6 7
query = Bar computer a
# j 1 2 3 4 5 6 7
i +--------------
1 | 1 1 2 3 3 3 3
2 | 0 0 1 2 2 2 3
3 | 0 0 1 2 2 2 3
4 | 0 0 0 1 1 1 2
5 | 0 0 0 0 0 0 1
6 | 0 0 0 0 0 0 1
7 | 0 0 0 0 0 0 1
此矩阵具有非增加列和非减少行,这通常是正确的。我们希望以值m遍历条目的下侧,因为进一步对应于更长的解决方案。算法如下。如果当前i,j具有所有查询词,则增加i;否则,增加j。
使用我们当前的数据结构,增加j很好,但增加i不是,因为我们的数据结构不支持删除。当查询单词的最后一个副本消失时,我们需要保持多集并递减num_found
而不是集合。
best = infinity
count = hash table whose entries are zero by default
for k = 1 to m
count[query[k]] = -1
num_found = 0
i = 1
j = 0
while true
if num_found == m
if j - i < best
best = j - i
best_i = i
best_j = j
count[text[i]] -= 1
if count[text[i]] == -1
num_found -= 1
i += 1
else
j += 1
if j > n
break
if count[text[j]] == -1
num_found += 1
count[text[j]] += 1
我们到达O(n)。最后渐近相关的优化是通过仅存储查询中的元素的计数来将额外空间使用从O(n)减少到O(m)。我会留下那个作为练习。 (此外,必须更加小心处理空查询。)
答案 1 :(得分:0)
对于这类问题,我建议使用Suffix Trees。