我遇到的问题类似于 Given a million integers, return the kth smallest element
。问题下面有一个给定的解决方案,我不确定为什么这是一个最佳解决方案。给定的解决方案涉及使用最小堆。所以最初,我认为这是有道理的,因为我们可以在恒定的时间内找到堆中的最小元素。但是在我想了一会儿之后,我想到了将数组中的元素插入堆中的成本。如果我的理解是正确的,插入是O(logN)
操作。但是,如果我们要插入N
元素,那么这应该花费我们O(NlogN)
时间。我们还使用了我们正在使用的堆的额外空间。所以我的问题是,为什么这是一个比排序数组更好的解决方案,并采用kth-1索引?
答案 0 :(得分:3)
在min heap中,在最坏的情况下单个插入是O(logN)
,因为只有在堆属性(父值应小于子节点)时才会产生成本被侵犯了。
有一个定理说,如果要将数组转换为堆(称为构建堆),则总体复杂度为O(N)
,而不是{{1 }}。您可以在Wikipedia for binary heap中阅读详细信息。
所以这种方法确实比简单地整理整个数组更好。由于对整个数组进行排序的时间复杂度为O(NlogN)
,因此使用最小堆方法时,总复杂度为O(NlogN)
,这在O(N+klogN)
较小时更为理想。
为了完整起见,我将描述为什么使用min-heap获取k
个最小元素的复杂性为k
。如果你想要最小的元素,你可以在恒定的时间内取根。但是第二个最小元素呢?然后,您需要删除最小的元素,然后恢复堆属性。然后根将是数组中删除了最小元素的最小元素,因此它是第二个最小元素。我们可以继续O(N+klogN)
次这样做以获得k
个最小的元素。由于恢复堆属性的操作为k
,因此构建堆的总复杂度为O(logN)
,然后继续删除最小元素O(N+klogN)
次。
答案 1 :(得分:1)
解决此问题的更好方法是使用max-heap of size K
。
我们可以遍历并比较数组的每个元素和最大堆的根,如果数组元素小于根,我们可以用元素替换root并堆化堆。此操作将花费log(K)
时间。我们将对数组的所有N
元素执行此操作。
堆的根将为您提供Kth
最小元素。
这样,时间复杂度就会变为O(NlogK)
。
答案 2 :(得分:1)
问题必须讨论整数的范围。
比如说范围是[0,10000]
意味着有重复。现在,如果我们使用排序,那么我们将需要排序一百万个整数!这将是O(nlogn)
在堆的情况下,由于重复由计数器(或链表)处理,我们在堆中只有一个10000
元素!一般来说,对于n
范围内的[0,m]
个整数,堆将O(mlogm)
时间m < n
。因此堆将是有效的。还有一件事,BUILD_HEAP算法不会使O(mlogm)
构建堆。这需要O(m)
时间。
当您将kth
元素插入堆时,HEAPFY将O(logk)
带到k < m
。
详细了解here
是的,存在O(n)
算法来查找数组中的第k个最小元素。它被称为selection algorithm
。 CLRS非常优雅地讨论它。阅读here和here
因此,取决于您是否根据m
和n
答案 3 :(得分:1)
首先让我们看看我们有什么选择。
第一个选项是sort和return第k个元素。 如果您可以对输入数组进行排序,则时间复杂度为O(n log n),空间复杂度为O(1)。
第二个选项是维护一个大小为k的堆。它在justhalf's answer中进行了解释。 时间复杂度为O(n log k),空间复杂度为堆的O(k)。
实际上,您可以在O(n)时间内堆积数组,然后弹出k个元素以找到第k个元素。 时间复杂度再次为O(n + k log n),空间复杂度为O(1)。
正如rcgldr的评论所指出的,有一种选择算法,其中O(n)时间和O(1)空间。
快速选择是查找第k个最小元素的常用方法。另一种选择是介绍选择。 - rcgldr
所以这取决于具体情况。例如,当使用sort比较使用堆和解决方案的解决方案时,您需要考虑k将是什么。有时k非常小,使用堆可能会有所帮助。
有时输入是一个流,你需要实时报告第k个元素,使用O(n log n)时间对数组进行排序,或者在O(n)时间内使用选择算法可能太慢。如果你使用堆,你可以在O(1)时间内得到答案。
您还需要考虑空间复杂性。 有时您可能不允许更改输入,因此如果使用排序或选择算法,则需要复制数组。它意味着O(n)空间与堆解的O(k)空间相比。
所以这完全取决于。你能改变输入数组吗?你可以花多少钱?这是一个实时查询以及查询的频率吗?输入是增长还是静止?在不同情况下,更好的解决方案会有所不同。