Algorithm 正数流中任何时间点的最小缺失数

Algorithm 正数流中任何时间点的最小缺失数,algorithm,data-structures,Algorithm,Data Structures,我们正在处理一个正整数流。在任何时候,我们都可以被问到一个问题,这个问题的答案是我们尚未见过的最小正数 可以假设有两个API void processNext(int val) int getSmallestNotSeen() 我们可以假设这些数字在[1,10^6]的范围内。让这个范围为N 这是我的解决办法 让我们看一个大小为10^6的数组每当调用processNext(val)时,我们将数组[val]标记为1。我们在这个数组上创建一个和段树。这将是段树中的点更新每当调用getSmallest

我们正在处理一个正整数流。在任何时候,我们都可以被问到一个问题,这个问题的答案是我们尚未见过的最小正数

可以假设有两个API

void processNext(int val)
int getSmallestNotSeen()
我们可以假设这些数字在[1,10^6]的范围内。让这个范围为N

这是我的解决办法

让我们看一个大小为10^6的数组

每当调用processNext(val)时,我们将数组[val]标记为1。我们在这个数组上创建一个和段树。这将是段树中的点更新

每当调用getSmallestNotSeen()时,我都会找到最小的索引j,使得sum[1..j]小于j。我使用二进制搜索找到j。

processNext(val)->O(1)

getSmallestNotSeen()->O((logN)^2)

我在想,如果有更好的办法。或者可以改进上述解决方案。

制作id->node(双链接列表的节点)的映射,并初始化10^6个节点,每个节点指向其邻居。将min初始化为1

processNext(val):检查节点是否存在。如果有,请删除它,并将它的邻居彼此指向对方。如果删除的节点没有左邻居(即最小的节点),请将最小节点更新为右邻居

getSmallestNotSeen():返回最小值

预处理是线性时间和线性内存。之后的一切都是常数时间。

您的解决方案需要O(N)个空间来保存数组和求和段树,并需要O(N)个时间来初始化它们;然后对这两个查询执行O(1)和O(log²N)。很明显,如果有很多查询,那么从长远来看,要跟踪到目前为止“看到”了哪些数字,没有比O(N)空间更好的了

但是,不同的数据结构可以提高查询时间。这里有三个想法:


自平衡二叉搜索树 初始化树以包含从1到N的每个数字;这可以在O(N)时间内完成,方法是从叶子向上建树;叶子有所有的奇数,然后它们被所有2模4的数连接起来,然后那些被4模8的数连接起来,依此类推。这棵树占了O(N)的空间

  • processNext
    通过在O(log N)时间内从树中删除数字来实现
  • getSmallestNotSeen
    通过在O(logn)时间内查找最左边的节点来实现
如果多次调用
getSmallestNotSeen
,这是一种改进,但是如果很少调用
getSmallestNotSeen
,那么您的解决方案会更好,因为它在O(1)而不是O(log 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))空间是渐近最优的应该很简单,所以哈希集是最好的选择。在O(1)摊销时间内,将哈希集与当前最小变量相结合以执行
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
}
时间复杂性:

  • 下一步处理:摊销
    O(1)
  • getSmallestNotSeen:
    O(1)
分析:

如果调用
processNext
k
,并且
n
min
中存储的最高值(可在
getSmallestNotSeen
中返回),则:

  • A
    将精确执行
    k
  • B
    将精确执行
    k+n
    次,并且
  • C
    将被精确执行
    n次
此外,
n
永远不会大于
k
,因为
min
要达到
n
,数组中需要有一个连续的
n
true
,数组中总共只能有
k
true
。因此,行
B
最多可以执行
2*k
次,行
C
最多可以执行
k

空间复杂性:

可以使用HashMap代替数组,而无需对
HashMap<int,bool> map
int min = 1

void processNext(int val) {
    if (val < min)
        return
    map.put(val, true)
    while (map.get(min) = true)
        map.remove(min)
        min++
}

int getSmallestNotSeen() {
    return min
}