我最近在接受采访时被问到这个问题。我有一个n元素的数组。该数组只有100个不同的值。我需要打印每个数字的出现次数。
1<=n<=10^6
1<=A[i]<=10^12
预期的空间复杂度为O(k),其中k
是数组中不同值的数量。
例如,1 2 3 2 1 4 3 2 4 2 3 1 2
;这里k
是4
。
首先我建议在stl中使用map,但他希望他实现我自己的数据结构。然后我建议对每个元素使用排序插入,就像在二叉搜索树中一样,但这会给出O(nlogn)的时间复杂度。他想要一个O(n)解决方案。我试着考虑任何哈希函数,但我无法想出任何这样的函数。我还试图考虑trie数据结构,但我将再次扫描每个数字的每个数字,从而再次给出O(nlogn)复杂度。有什么办法可以解决这个问题?
答案 0 :(得分:1)
哈希表不保证O(n * k)的理论复杂度。但制作这样的东西很容易。
首先,我们需要对值概率分布做一些假设 - 让它统一(或者我们需要一些专门的散列函数)。
接下来,让我们选择哈希表大小,例如201个条目(因此它将小于50%)。
接下来,让哈希函数只是MonoGame.Framework\bin
。
然后使用具有201个条目对的开放寻址哈希表H []:A [i]或NULL;频率值。
答案 1 :(得分:1)
我认为哈希表是一个很好的解决方案,但我想面试官期望你建立自己的哈希表。
这是我在Python中提出的解决方案。我使用mod 100
作为哈希函数并使用Separate chaining来处理冲突。
import random
N = random.randint(1, 10**6)
K = 100
HASH_TABLE_SIZE = 100
distinct = [random.randint(1, 10**12) for _ in range(K)]
numbers = [random.choice(distinct) for _ in range(N)]
hash_table = [[] for _ in range(HASH_TABLE_SIZE)]
def hash(n):
hash_key = n % HASH_TABLE_SIZE
bucket = hash_table[hash_key]
for value in bucket:
if value[0] == n:
value[1] += 1
return
bucket.append([n, 1])
for number in numbers:
hash(number)
for bucket in hash_table:
for value in bucket:
print('{}: {}'.format(*value))
修改强>
稍微解释一下代码:
我的哈希表是一个100个元素的数组。数组中的每个条目都是(number, count)
个条目的列表。要散列数字,我将其值取模100来查找数组的索引。我扫描那个桶中已有的数字,如果它们中的任何一个与当前数字匹配,我会增加它的计数。如果我找不到该号码,我会在列表中附加一个新条目,其中包含数字和初始计数为1。
从视觉上看,数组看起来像这样:
[
[ [0, 3], [34500, 1] ]
[ [101, 1] ],
[],
[ [1502, 1] ],
...
]
请注意,在索引 n 中,存储在存储桶中的每个值等于 n (mod 100)。平均而言,每个桶只有一个值,因为阵列中最多有100个不同的值和100个元素。
要打印出最终的计数,所需要的只是遍历阵列和每个桶中的每个条目并打印出来。
编辑2
这是一个稍微不同的实现,它使用Open addressing代替线性探测。我想我其实更喜欢这种方法。
hash_table = [None] * HASH_TABLE_SIZE
def hash(n):
hash_key = n % HASH_TABLE_SIZE
while hash_table[hash_key] is not None and hash_table[hash_key][0] != n:
hash_key = (hash_key + 1) % HASH_TABLE_SIZE
if hash_table[hash_key] is None:
hash_table[hash_key] = [n, 1]
else:
hash_table[hash_key][1] += 1
for number in numbers:
hash(number)
for entry in hash_table:
print('{}: {}'.format(*entry))
注意:如果实际上有超过100个不同的数字,此代码将失败。 (它将永远挂起,试图在阵列中找到一个空位。)保持检测到这种情况(例如,一旦你在阵列中走了整整一圈)并引发异常将是很好的。
答案 2 :(得分:1)
实际上,你错了,trie会给你O(N)
复杂性。
trie的一次插入/查找/擦除操作需要O(L)
时间,其中L
是推入此trie的字符串的长度。幸运的是,您只需插入不超过1万亿的数字,这意味着L
不大于log(10^12)
(对数基数取决于您在此系列中使用的计数系统。我个人会选择{{1} }或256
取决于此结构在整个系统的哪个部分发挥作用。)
总结一下,根据65536
的定义,您需要O(N) * O(log(10^12))
等于O(N)
。