算法谜题面试

Posted

技术标签:

【中文标题】算法谜题面试【英文标题】:Algorithm puzzle interview 【发布时间】:2012-04-17 13:42:10 【问题描述】:

找到了这个面试题,想不出比O(N^2 * P)更好的算法:

给定一个由 P 个自然数组成的向量 (1,2,3,...,P) 和另一个长度为 N 的向量,其元素来自第一个向量,找到第二个向量中最长的子序列,使得所有元素是均匀分布的(具有相同的频率)。

示例:(1,2,3) 和 (1,2,1,3,2,1,3,1,2,3,1)。最长的子序列在区间 [2,10] 中,因为它包含了第一个序列中频率相同的所有元素(1 出现 3 次,2 出现 3 次,3 出现 3 次)。

时间复杂度应该是O(N * P)。

【问题讨论】:

子序列必须是连续的吗? 是的,子序列 V[i..j] 由以下元素组成:V[i],V[i+1],..V[j]。 【参考方案1】:

“子序列”通常表示不连续。我假设您的意思是“子列表”。

这是一个 O(N P) 算法假设我们可以散列(不需要假设;我们可以改为基数排序)。扫描数组,为每个数字保留一个运行总计。以您为例,

  1  2  3
 --------
  0  0  0
1 
  1  0  0
2
  1  1  0
1
  2  1  0
3
  2  1  1
2
  2  2  1
1
  3  2  1
3
  3  2  2
1
  4  2  2
2
  4  3  2
3
  4  3  3
1
  5  3  3

现在,通过减去最小元素来标准化每一行。结果是

 0: 000
 1: 100
 2: 110
 3: 210
 4: 100
 5: 110
 6: 210
 7: 100
 8: 200
 9: 210
10: 100
11: 200.

准备两个哈希,将每一行映射到它出现的第一个索引和它出现的最后一个索引。遍历键并取最大的最后一个 - 第一。

000: first is at 0, last is at 0
100: first is at 1, last is at 10
110: first is at 2, last is at 5
210: first is at 3, last is at 9
200: first is at 8, last is at 11

最佳键是 100,因为它的子列表长度为 9。子列表是第 (1+1) 个元素到第 10 个元素。

这是因为子列表是平衡的当且仅当它的第一个和最后一个非标准化直方图相同直到添加一个常数,当且仅当第一个和最后一个标准化直方图相同时才会发生。

【讨论】:

在 N 行中搜索每行开始和结束将花费 O(N^2)。除此之外似乎有效。 散列/基数排序的全部意义在于我们不必对许多可能性进行二次搜索。 @WeaselFox:您可以只浏览列表一次,为每个条目检查代码(例如:200),如果它是新代码集索引,则为第一个,最后一个,否则仅作为最后一个。您还可以存储当前的最后一个最大值,因此在迭代结束时您有解决方案。实际上,您甚至不必存储最后一个索引。 在更高级别上,我将索引按其对应的规范化行进行分区。这最容易通过散列来解释,但由于每个条目都介于 0 和 N 之间,我们可以在 O(N P) 时间内对索引进行基数排序,然后通过仅按排序顺序比较相邻元素来进行分区。 @Downvoter 很抱歉破坏了你最喜欢的面试问题。 (或者您有合法的投诉吗?)【参考方案2】:

如果内存使用不重要,那很容易......

您可以给矩阵维度N*p 并在列(i) 中保存与p(i) 之间查找的元素数量相对应的值第二个向量中的元素...

完成矩阵后,您可以搜索列i,列i 中的所有元素都没有不同。最大的i 就是答案。

【讨论】:

你真的认为有人会理解这一点吗? 我认为 Karoly 的意思是 - 欢迎来到 Stack Overflow,您的答案还不清楚。 对不起,我不会说英语 别担心,你需要做的就是多解释你的想法。举个例子,看看其他人在说什么,并尝试将您的解决方案与他们的解决方案联系起来 - 例如,听起来您的答案与上面的答案相似,因此您可能走在正确的轨道上。使用您拥有的任何工具,不要气馁。需要一段时间才能感受这里的一切 @amin k:我加入这里的一个原因是为了提高我的英语水平。我知道第一次以清晰的风格写作有多难,只是尽力做到最好;) 【参考方案3】:

通过随机化,您可以将其归结为线性时间。这个想法是用一个随机整数替换每个 P 值,使得这些整数总和为零。现在寻找两个相等的前缀和。这允许一些误报的可能性很小,我们可以通过检查我们的输出来补救。

在 Python 2.7 中:

# input:
vec1 = [1, 2, 3]
P = len(vec1)
vec2 = [1, 2, 1, 3, 2, 1, 3, 1, 2, 3, 1]
N = len(vec2)
# Choose big enough integer B.  For each k in vec1, choose
# a random mod-B remainder r[k], so their mod-B sum is 0.
# Any P-1 of these remainders are independent.
import random
B = N*N*N
r = dict((k, random.randint(0,B-1)) for k in vec1)
s = sum(r.values())%B
r[vec1[0]] = (r[vec1[0]]+B-s)%B
assert sum(r.values())%B == 0
# For 0<=i<=N, let vec3[i] be mod-B sum of r[vec2[j]], for j<i.
vec3 = [0] * (N+1)
for i in range(1,N+1):
    vec3[i] = (vec3[i-1] + r[vec2[i-1]]) % B
# Find pair (i,j) so vec3[i]==vec3[j], and j-i is as large as possible.
# This is either a solution (subsequence vec2[i:j] is uniform) or a false
# positive.  The expected number of false positives is < N*N/(2*B) < 1/N.
(i, j)=(0, 0)
first = 
for k in range(N+1):
    v = vec3[k]
    if v in first:
        if k-first[v] > j-i:
            (i, j) = (first[v], k)
    else:
        first[v] = k
# output:
print "Found subsequence from", i, "(inclusive) to", j, "(exclusive):"
print vec2[i:j]
print "This is either uniform, or rarely, it is a false positive."

【讨论】:

好主意!尽管如此,您的算法仍然是 O(N*P) ,就像 uty 的答案一样,但它更节省空间。顺便说一句:如果您主要采用大于 N 的素数,则可以减少误报的可能性。不幸的是,其中一个替代项不能是素数,因为您需要总和为零。【参考方案4】:

这是一个观察:你不能得到一个长度不是P 乘法的均匀分布的序列。这意味着您只需检查 N 的子序列 P2P3P... long - (N/P)^2 这样的序列。

【讨论】:

然后如果你很聪明,你会得到一个 O(N^2/P) 的解决方案.. 不幸的是他需要更好【参考方案5】:

您可以通过增强 uty 的解决方案将其缩短到 O(N) 时间,而不依赖于 P。

对于每一行,不是存储每个元素的标准化计数,而是存储标准化计数的哈希值,同时仅保留当前索引的标准化计数。在每次迭代期间,您需要首先更新归一化计数,如果计数的每次递减在递增时支付,则其摊销成本为 O(1)。接下来,您重新计算哈希。这里的关键是散列需要在被散列的元组的元素之一递增或递减后轻松更新。

this question 的答案中显示了至少一种有效地进行这种散列的方法,并具有良好的理论独立性保证。请注意,计算指数以确定要添加到散列的数量的 O(lg P) 成本可以通过预先计算以素数为模的指数来消除,预计算的总运行时间为 O(P),给出总运行时间 O(N + P) = O(N)。

【讨论】:

以上是关于算法谜题面试的主要内容,如果未能解决你的问题,请参考以下文章

面试笔试算法目录

建议网站练习 C/C++ 算法/谜题 [关闭]

数据结构与算法之深入解析“滑动谜题”的求解思路与算法示例

算法谜题二手套选择

算法谜题二手套选择

UVa 11549 计算器谜题(Floyd判圈算法)