制作一个id->节点(双向链接列表的节点)的映射,并初始化10 ^ 6个节点,每个节点都指向其邻居。将最小值初始化为一。
processNext(val):检查节点是否存在。如果是这样,请将其删除,并将其邻居指向彼此。如果您删除的节点没有左邻居(即最小),则将最小值更新为右邻居。
getSmallestNotSeen():返回最小值
预处理是线性时间和线性内存。之后的一切都是固定的时间。
,
bool array[10^6] = {false,false,... }
int min = 1
void processNext(int val) {
array[val] = true // A
while (array[min]) // B
min++ // C
}
int getSmallestNotSeen() {
return min
}
时间复杂度:
- processNext:摊销
O(1)
- getSmallestNotSeen:
O(1)
分析:
如果processNext
被调用k
次,并且n
是存储在min
中的最大值(可以在getSmallestNotSeen
中返回),则:
- 第
A
行将准确执行k
次,
- 第
B
行将准确执行k + n
次,并且
- 第
C
行将准确执行n
次。
此外,n
永远不会大于k
,因为要使min
达到n
,就需要连续不断的n
{{ 1}}在数组中,总共只能有true
k
在数组中。因此,行true
最多可以执行B
次,行2 * k
最多可以执行C
次。
空间复杂度:
可以使用HashMap而不使用数组,而无需对伪代码进行任何其他更改(HashMap中不存在的键应评估为k
)。那么,空间复杂度为false
。此外,您可以修剪小于O(k)
的键,从而在某些情况下节省空间:
min
如果流值稳定增加,则这种修剪技术可能最有效。
,
如果processNext
调用的数量(即流的长度)与 N 的范围相比非常小,则可以通过存储连续的范围,而不是所有可能的单个数字。当 N 的范围可能更大时,例如[1,2 64 -1]
,这也很有趣
数据结构
我建议使用以[开始,结束]范围为元素并且具有自平衡功能(例如AVL,红黑...)的二进制搜索树。
算法
使用一个(根)节点初始化树:[1,Infinity]
每当用val
提取新值processNext
时,就使用二进制搜索找到包含val
的范围[start,end]。
如果范围的大小为1(因此仅包含val
),请执行删除该节点的操作(根据树规则)
否则,如果val
是范围的边界值,则只需更新该节点中的范围,不包括val
。
否则,将范围分为两部分。使用两个范围之一(由余额信息决定)更新节点,然后让另一个范围向下筛选到新的叶子(并在需要时重新平衡)。
在树中维护对具有最小起始值的节点的引用。仅当此节点在processNext
期间被删除时,才需要在树中向上或向下遍历以找到下一个(按顺序)节点。当节点分裂时(请参见上文),并确定将下部放入新的叶子中,则需要将引用更新为该叶子。
getSmallestNotSeen
函数将从该最小范围节点返回起始值。
时间和空间的复杂性
空间复杂度为 O(S),其中 S 是流的长度
processNext
的时间复杂度为 O(log(S))
getSmallestNotSeen
的时间复杂度为 O(1)
最佳情况下的空间和时间复杂度是 O(1)。当流具有连续的整数(递增或递减)时,会发生这种最佳情况
,
您的解决方案需要O(N)空间来保存数组和总和段树,并且需要O(N)时间来初始化它们;然后是两个查询的O(1)和O(log²N)。很显然,如果要查询很多,从长远来看,要跟踪“看到”的数字,您不能做得比O(N)空间更好。
但是,不同的数据结构可以改善查询时间。这是三个想法:
自平衡二进制搜索树
初始化树以包含从1到N的每个数字;这可以在O(N)时间内通过从叶子上盖起树来完成;叶子具有所有奇数,然后它们由2 mod 4的所有数字连接,然后由4 mod 8的数字连接,依此类推。该树占用O(N)空间。
-
processNext
是通过在O(log N)时间中从树中删除数字来实现的。
-
getSmallestNotSeen
通过在O(log N)时间中找到最左边的节点来实现。
如果多次调用getSmallestNotSeen
,这是一个改进,但是如果很少调用getSmallestNotSeen
,则您的解决方案会更好,因为它在O(1)中而不是在O(1)中执行processNext
登录N)。
双向链接列表
按顺序初始化一个包含数字1到N的双向链接列表,并创建一个大小为N的数组,其中包含指向每个节点的指针。这需要O(N)空间,并且需要O(N)时间。初始化一个变量,将缓存的最小值设为1。
通过在数组中查找对应的列表节点并将其从列表中删除来实现-
processNext
。如果删除的节点没有前任节点,则将缓存的最小值更新为后继节点保留的值。这是O(1)时间。
-
getSmallestNotSeen
是通过以O(1)时间返回缓存的最小值来实现的。
这也是一种改进,尽管所涉及的常数可能更高,但渐近严格地更好。拥有大小为N的数组以及大小为N的双向链接列表的开销很大。
哈希集
其他解决方案的时间要求主要取决于它们的初始化阶段,这需要O(N)时间。另一方面,初始化一个空的哈希集是O(1)。和以前一样,我们还初始化一个将当前最小值保持为1的变量。
-
processNext
通过在O(1)摊销时间内将数字插入集合中来实现。
-
getSmallestNotSeen
通过将当前最小值递增直到不再存在于集合中来进行更新,然后将其返回。哈希集的成员资格测试为O(1),所有查询的增量数受调用processNext
的次数限制,所以这也是O(1)的摊销时间。
渐近而言,此解决方案花费O(1)时间进行初始化和查询,并且使用O(min(Q,N))空间,其中Q是查询数,而其他解决方案无论如何都使用O(N)空间
我认为证明O(min(Q,N))空间是渐近最优的应该是很直接的,因此哈希集被证明是最佳选择。功劳归于Dave,用于将哈希集与当前最小变量组合在一起,以在O(1)的摊销时间内完成getSmallestNotSeen
。
本文链接:https://www.f2er.com/3106750.html