在大小为 N 的数组的每 k 个元素中查找最小和第二小的元素

Posted

技术标签:

【中文标题】在大小为 N 的数组的每 k 个元素中查找最小和第二小的元素【英文标题】:Find smallest & second smallest element in every k elements of an array with size N 【发布时间】:2015-12-02 05:41:21 【问题描述】:

我试图在 N 大小的数组的 k 个元素中找到最小和第二小的元素(没有排序和最小/最大堆)。

使用传统方法首先从0th 元素开始,在第一个k 元素中找到最小和第二小的元素,然后将起点移动1 并重复该过程。但它的复杂性是O(Nk)。如果可能的话,我需要一个复杂的解决方案O(N)。对此有何建议?

在 Jubobs 的评论之后编辑:例如如果说数组是12, 1, 78, 90, 57, 89, 56k3,那么对于第一个k 元素(12, 1, 78) 最小元素将是1,第二个最小元素将是12。对于第二个k 元素(1, 78, 90),最小的元素将是1,第二小的元素将是78,依此类推。

以下是我用O(Nk)复杂度写的sn-p代码:

int j=0;
while(j < input.length-k+1) 
    int i=j;
    for(i=j; i < j+k; i++) 
        if(input[i] < min) 
            min2 = min;
            min = input[i];
         else if(input[i] > min && input[i] < min2) 
            min2 = input[i];    
                           
    

【问题讨论】:

这与只获取最小值(或最大值)并没有太大的不同,后者被问了很多次。 在哪里推广j,如果结果是整个数组的minmin2,为什么还需要K 看***.com/questions/8031939/…中的动态规划解法 【参考方案1】:

您可以使用保持排序的deque。

在每一步,如果双端队列中的第一个元素 (d.front.index) 相对于当前步骤早于 k 步骤,则将其弹出 (d.popFront())。

然后,当数组中当前位置的元素小于双端队列中的最后一个元素(d.back.value)时,从双端队列中弹出元素(d.popBack())。

最后,将当前值添加到双端队列的末尾 (d.pushBack())。

在每一步,d.front.value 将是[step - k + 1, step] 的最小值。

您可以将双端队列存储为大小为k 的链表。然后您将始终可以访问其中的第二个元素,这将是[step - k + 1, step] 中的第二小元素。如果由于弹出每个元素而最终只有一个元素,则必须小心。在这种情况下,弹出的可能是未来查询的第二小。您可以将它们保存在另一个列表中,与双端队列类似,请参见下面的示例。

这是O(n) amortized,因为数组中的每个元素最多会进入和离开双端队列一次。它可能看起来像O(nk),因为你会有一些嵌套循环,但如果你考虑一下操作的总数,你会发现它实际上是O(n)

伪代码

for i = 0, i < n:
    if not d.empty and i - d.front.index >= k:
      d.popFront()
    while not d.empty and d.back.value > a[i]:
      d.popBack()

    d.pushBack(index = i, value = a[i])

    output d.front.value as the minimum value in [i - k + 1, i]

跟踪第二个最小值的代码留作练习。

你的例子:

a = 12, 1, 78, 90, 57, 89, 56, k = 3

d = 12
d = 1 (12 popped, track this)
d = 1, 78 => we have to output smallest and second smallest in [0, 2].
            => smallest is always the first in the deque, so 1
            => second = min(12 [this was popped], 78) = 12
d = 1, 78, 90)
            => smallest 1, second is 78 (12 is too old now)
d = 57
            => 1 popped for being too old, the others for being too large
            => smallest = 57, second = 78 (last popped)
d = 57, 89
            => smallest = 57, second = 89
d = 56
            => smallest = 56, second = 57

基本上,您保留第二小的数组。这将包含尚未太旧的弹出值。这些也将按降序排序。

此方法的示例运行,其中d2 是第二个数组:

a = 12, 1, 78, 90, 57, 89, 56 

d = 12,           d2 = 
d = 1,            d2 = 12
d = 1, 78,        d2 = 12
  => output 1, 12
d = 1, 78, 90,    d2 =  - 12 was too old
  => output 1, 78
d = 57            d2 = 90, 78
  => output 57, 78
d = 57, 89        d2 = 90 - 78 too old
  => output 57, 89 (if we had 91 instead of 89, we'd have output the 90 in d2)
d = 56            d2 = 89, 57
  => output 56, 57

【讨论】:

您没有计算算法中保持排序的部分 @FrankM 我是。您通过推送/弹出操作对它们进行排序,而不是通过应用排序算法,所以它是O(n) 感谢 IVIad。这有帮助。 :)

以上是关于在大小为 N 的数组的每 k 个元素中查找最小和第二小的元素的主要内容,如果未能解决你的问题,请参考以下文章

在 n+2k-3 个比较中查找大小为 (2^k +1) 的数组中的第三大元素

亚马逊面试问题

合并k个有序数组

13常见算法数组元素的区间查找

查找数组中的 K 个最小元素

c++ 快排思想查找第k小数……注意是小