我看到了这个面试题,但是我想不出比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出现了三次,2出现了三次,3出现了三次)。
时间复杂度应该为O(N * P)。
我看到了这个面试题,但是我想不出比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出现了三次,2出现了三次,3出现了三次)。
时间复杂度应该为O(N * P)。
"子序列"通常表示非连续的。我会假设你的意思是"子列表"。
这里有一个O(NP)算法,假设我们可以进行哈希操作(其实不需要这个假设;我们可以使用基数排序代替)。扫描数组并针对每个数字保持一个运行总和。以您的示例为例:
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.
准备两个哈希表,将每一行映射到其第一次出现的索引和最后一次出现的索引。遍历键并选择具有最大last - first的键。
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个元素。
这是因为当且仅当子列表的第一个和最后一个未规范化的直方图在添加常量的情况下相同时,子列表是平衡的,这种情况只会发生在第一个和最后一个归一化直方图相同时。
# 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."
这里有一个观察结果:你无法得到一个长度不是P
的均匀分布序列。这意味着你只需要检查N
的子序列,它们的长度为P
,2P
,3P
... - 这样的序列总共有(N/P)^2
个。
通过增强uty的解决方法,您可以将其缩短到O(N)时间,并且不依赖于P。
对于每一行,存储每个元素的归一化计数的哈希值,同时仅保留当前索引的归一化计数,而不是存储每个元素的归一化计数。在每次迭代中,您需要首先更新归一化计数,如果在增加计数时支付减少计数,则其摊销成本为O(1)。接下来重新计算哈希值。关键在于哈希值需要在哈希元组的一个元素被增加或减少后容易进行更新。
至少有一种有效且具有良好理论独立性保证的哈希方式,可以在this question的答案中找到。请注意,可以通过预先计算模质数的指数来消除计算指数以确定要添加到哈希值的数量的O(lg P)成本,并具有总运行时间为O(P)的预计算时间,从而获得总运行时间为O(N + P)= O(N)的结果。