我喜欢你的堆想法。实际上,我认为它会在您列出的时间范围内起作用,并且您的分析中存在一些小故障。
假设您执行以下操作:在数组中构建一个就地堆,然后将最少 k 个元素出列,将剩余的 n - k 个元素留在数组中的位置。如果您考虑元素将在哪里结束,您应该将数组中的 k 个最小元素按升序存储在数组的后面,而剩余的 n - k 个元素将按堆顺序存储在前面。如果您无法看到这一点,请考虑堆排序的工作原理 - 在 k 个出队之后,最大的 k 个元素在后面按降序排列,其余元素在前面按堆排序。在这里,我们将最小堆换成了最大堆,因此出现了奇怪的排序。因此,如果你在最后反转数组,你应该在前面有 k 个最小元素以升序排列,然后是 n - k 个剩余元素。
这将正确找到k个最小的元素,并且运行时间确定如下:
- heapify 的成本:O(n)
- k 出队成本:O(k log n)
- 反转数组的成本:O(n)
- 总成本:O(n + k log n)
现在,假设 k ≤ n / log n。然后运行时是
O(n + k log n) = O(n + (n / log n) log n) = O(n)
所以你完成了!该算法工作得很好。另外,它需要 O(1) 辅助空间(堆可以就地构建,并且可以在 O(1) 空间中反转数组)。
不过,你可以做得更好。@timrau 在评论中建议您使用快速选择(或更一般地说,任何线性时间选择算法)。这些算法重新排列数组,以某种顺序将最低 k 个元素放在数组的前 k 个槽中,并将剩余的 n - k 个元素按某种顺序放在最后 n - k 个槽中。这样做需要时间 O(n) 而不管 k (漂亮!)。所以假设你这样做,然后只对前 k 个元素进行排序。这需要时间 O(n + k log k),渐近优于 O(n + k log n) 时间的基于堆的算法。
在已知的线性时间选择算法中,如果你小心的话,快速选择和中位数算法都可以就地实现,因此这种方法所需的总空间是 O(1)。