Solution: Keeping Only the k Largest
The last attempt kept all n elements in the heap, but we only need the top k. So, as we go through the array, let’s keep only the k largest elements seen so far. Once we have seen everything, the smallest of those k is the kth largest overall.
Keeping only k means that the element we remove is the smallest one, not the largest. So we switch from a max-heap to a min-heap: each time we add an element, if the heap then holds more than k elements, its smallest cannot be among the k largest, so we remove it. A min-heap makes that cheap, since it keeps its smallest element at the root.
public static int findKthLargest(int[] numbers, int k) {
PriorityQueue<Integer> heap = new ArrayHeap<>();
for (int number : numbers) {
heap.add(number);
if (heap.size() > k) {
heap.remove(); // drop the smallest; it is not among the k largest
}
}
return heap.peek(); // the smallest of the k largest
}
Let’s trace it on numbers = [3, 1, 5, 2, 4] with k = 2. Once it fills, the heap holds k = 2 elements, plus one more for a moment after each add. As before, the diagrams show the contents smallest-first, not in the heap’s array order.
The first two elements just fill the heap. We add 3, then 1; the size is still within k = 2, so nothing is removed.
┌───┐
│ 3 │ size = 1
└───┘
▲
peek → 3 (min)
┌───┬───┐
│ 1 │ 3 │ size = 2
└───┴───┘
▲
peek → 1 (min)
We add 5. The heap now holds three elements, one too many. peek points at 1, which cannot be among the two largest, so we remove it.
┌───┬───┬───┐
│ 1 │ 3 │ 5 │ size = 3
└───┴───┴───┘
▲
peek → 1 (min)
┌───┬───┐
│ 3 │ 5 │ size = 2
└───┴───┘
▲
peek → 3 (min)
We add 2. The size is three again, and peek points at 2, so we remove it.
┌───┬───┬───┐
│ 2 │ 3 │ 5 │ size = 3
└───┴───┴───┘
▲
peek → 2 (min)
┌───┬───┐
│ 3 │ 5 │ size = 2
└───┴───┘
▲
peek → 3 (min)
We add the last element, 4. The size is three once more, so we remove the smallest, 3.
┌───┬───┬───┐
│ 3 │ 4 │ 5 │ size = 3
└───┴───┴───┘
▲
peek → 3 (min)
┌───┬───┐
│ 4 │ 5 │ size = 2
└───┴───┘
▲
peek → 4 (min)
The heap ends holding {4, 5}, the two largest elements, and peek returns 4, the second largest.
What it costs
The loop runs n times, and each pass does an add, a size, and sometimes a remove. The heap never holds more than k + 1 elements, so its height is about , and each add is amortized and each remove is . The whole solution is . It also needs only space, not , and it leaves the caller’s array unchanged.
At this can beat sorting’s when k is much smaller than n. If k grows in proportion to n, the two have the same asymptotic order.
For the kth smallest element, flip the order: keep a size-k max-heap, built by passing a reversed comparator, and its peek is the kth smallest.