我有一些代码的不同实现,可以在未排序的数组中找到第K个最大元素。我使用的三个实现都使用最小/最大堆,但是我很难弄清其中一个的运行时复杂性。
实施1:
int findKthLargest(vector<int> vec, int k)
{
// build max-heap
make_heap(vec.begin(), vec.end());
for (int i = 0; i < k - 1; i++) {
// move max. elem to back (from front)
pop_heap(vec.begin(), vec.end());
vec.pop_back();
}
return vec.front();
}
实施2:
int findKthLargest(vector<int> vec, int k)
{
// max-heap prio. q
priority_queue<int> pq(vec.begin(), vec.end());
for (int i = 0; i < k - 1; i++) {
pq.pop();
}
return pq.top();
}
实施3:
{{1}}
从我的阅读中,我假设第二个运行时为O(n)+ O(klogn)= O(n + klogn)。这是因为建立最大堆是在O(n)中完成的,如果我们将其弹出“ k”次,则将其弹出将需要O(logn)* k。
但是,这是我感到困惑的地方。对于第一个具有最小堆的堆,我假设构建堆的对象是O(n)。由于这是最小堆,因此较大的元素在后面。然后,弹出后元素“ k”次将花费k * O(1)= O(k)。因此,复杂度为O(n + k)。
同样,对于第三个,我假设复杂度也是O(n + klogn),其推理与我对max-heap的解释相同。
但是,一些消息来源仍然说,使用堆/ pqs不能比O(n + klogn)更快地完成此问题!在我的第一个示例中,我认为这种复杂度是O(n + k)。如果我错了纠正我。需要帮助。
答案 0 :(得分:0)
正确实现,从最小堆中获取第k个最大元素是O((n-k)* log(n))。从最大堆中获取第k个最大元素是O(k * log(n))。
您的第一个实现根本不正确。例如,如果您想从堆中获取最大的元素(k == 1),则循环体将永远不会执行。您的代码假定向量中的最后一个元素是堆上的最大元素。那是不对的。例如,考虑堆:
1
3 2
那是一个完全有效的堆,用向量[1,3,2]
表示。您的第一个实现将无法从该堆中获得第一或第二大元素。
第二种解决方案似乎可行。
您的前两个解决方案最终从vec
中删除了项目。那是你想要的吗?
第三个解决方案是正确的。它需要O(n)来构建堆,而O((k-1)log n)则需要删除(k-1)个最大项。然后O(1)访问剩余的最大项目。
还有 另一种方法,在实践中可能更快。这个想法是:
build a min-heap of size k from the first k elements in vec
for each following element
if the element is larger than the smallest element on the heap
remove the smallest element from the heap
add the new element to the heap
return element at the top of the heap
这是O(k)以建立初始堆。然后,剩下的项目在最坏的情况下为O((n-k)log k) 。最坏的情况发生在初始向量按升序排列时。这种情况很少发生。实际上,一小部分项目被添加到堆中,因此您不必进行所有这些删除和插入操作。
某些堆实现具有heap_replace
方法,该方法结合了删除顶部元素和添加新元素的两个步骤。这将复杂性降低了一个恒定的因素。 (即,不是先移除O(log k),然后插入O(log k),而是对顶部元素进行恒定时间替换,然后是O(log k)将其向下筛选到堆中)。