以下是访谈问题:为一系列整数设计数据结构{1,...,M}(数字可以重复)支持insert(x),delete(x)和返回模式,返回最多经常编号。
面试官说我们可以在O(1)中进行O(M)中预处理的所有操作。他还接受我可以在O(log(n))中执行insert(x)和delete(x),在O(1)中使用O(M)中的预处理进行返回模式。
但是我只能在O(n)中给出插入(x)和删除(x)以及在O(1)中返回模式,实际上我怎么能给O(log(n))或/和O(1) )在插入(x)和删除(x)中,并在O(1)中返回模式,并在O(M)中预处理?
答案 0 :(得分:1)
当您听到O(log X)
操作时,首先想到的结构应该是binary search tree和heap。供参考:(因为我专注于下面的一堆)
堆是一种专门的基于树的数据结构,它满足堆属性:如果A是B的父节点,那么节点A的密钥是相对于节点B的密钥排序的,具有相同的排序。堆。 ...父节点的密钥总是大于或等于子节点的密钥,最高密钥在根节点中(这种堆称为最大堆)....
二进制搜索树不允许在O(M)
中构造(来自未排序的数据),所以让我们看看我们是否可以使堆工作(你可以在O(M)
中创建一个堆)。
显然,我们希望最常见的数字位于顶部,因此这个堆需要使用频率作为其排序。
但是这给我们带来了一个问题 - insert(x)
和delete(x)
都要求我们查看整个堆以找到正确的元素。
现在你应该考虑“如果我们在树中从索引到位置有某种映射?”,这正是我们将要拥有的。如果所有/大多数M
元素都存在,我们可以只有一个数组,每个索引i
的元素是指向堆中节点的指针。如果正确实现,这将允许我们在O(1)
中查找堆节点,然后我们可以对其进行适当修改,然后移动O(log M)
同时insert
和delete
如果只存在少数M
元素,则用(hash-)映射(整数到堆节点)替换数组可能是个好主意。
返回该模式需要O(1)
。
O(1)
肯定要困难得多。
我想到了以下结构:
3 2
^ ^
| |
5 7 4 1
12 14 15 18
要解释这里发生了什么 - 12
,14
,15
和18
对应频率,上面的数字对应于具有所述频率的元素,因此,5
和3
的频率均为12
,7
和2
的频率为14
等。
这可以实现为双链表:
/-------\ /-------\
(12) <-> 5 <-> 3 <-> (13) <-> (14) <-> 7 <-> 2 <-> (15) <-> 4 <-> (16) <-> (18) <-> 1
^------------------/ ^------/ ^------------------/ ^------------/ ^------/
您可能会注意到:
我填写了遗失的13
和16
- 这些是必要的,否则我们必须在执行insert
时更新频率相同的所有元素(在此示例中,在执行5
时,您需要更新13
以指向insert(3)
,因为13
尚不存在,所以它会指向{ {1}})。
我跳过14
- 这只是空间使用方面的优化 - 这使得此结构占用17
空间,而不是O(M)
。跳过一个数字的确切条件就是它在频率上没有任何元素,或者比其频率少一个元素。
链接列表上方发生了一些奇怪的事情。这些只是意味着O(M + MaxFrequency)
也指向5
,13
也指向7
,即每个元素也保留指向下一个频率的指针。
链接列表下面发生了一些奇怪的事情。这些只是意味着每个频率保持指向它之前的频率的指针(这比每个元素保持指向它自己和下一个频率的指针更节省空间。)
与上述解决方案类似,我们在此结构中保留整数到节点的映射(数组或映射)。
要插入:
要删除:
获得模式:
返回最后一个节点。
答案 1 :(得分:0)
由于范围是固定的,为简单起见,我们举个例子M = 7(范围是1到7)。所以我们需要最多3位来表示每个数字。
0 - 000 1 - 001 2 - 010 3 - 011 4 - 100 5 - 101 6 - 110 7 - 111
现在创建一个b树,每个节点都有2个子节点(如Huffmann编码算法)。每个叶子将包含每个数字的频率(最初它将为0)。这些节点的地址将保存在一个数组中,键作为索引(即节点1的地址将在数组中的索引1处)。
通过预处理,我们可以在O(M)时间内执行插入,删除O(1),模式。