Problem overview
You write the class KthLargest. Its constructor receives k and the numbers that have already arrived. Each call to Add(val) takes in one more number and returns the k-th largest of everything seen so far. Equal numbers count separately: in [8, 7, 7, 5] the 2nd largest and the 3rd largest are both 7.
Take k = 3 and the starting numbers [4, 5, 8, 2]. Calling Add with 3, 5, 10, 9 and 4 returns this:
Add(3) -> 4
Add(5) -> 5
Add(10) -> 5
Add(9) -> 8
Add(4) -> 8
After Add(3) the numbers are 2, 3, 4, 5, 8. The three largest are 8, 5 and 4, so the 3rd largest is 4. The last call adds a 4, which is smaller than the current 3rd largest (8), so the answer stays 8.
With k = 1 and no starting numbers, adding -3, -2, -4, 0 and 4 returns -3 -2 -2 0 4: each answer is the largest number so far.
Approach: keep only the k largest
The direct way is to store every number, sort the list on each Add, and read the element at position k from the top. If n numbers have arrived, each call costs O(n log n). With up to 100,000 starting numbers and 100,000 calls, that is far too slow.
The key observation is that most numbers can never be the answer. New numbers only push the k-th largest up, never down. So a number that is not among the k largest now will not be among them later, and it can be thrown away. You only need to remember the k largest numbers.
Keep those k numbers in a min-heap, a structure that always hands out its smallest value first. The smallest of the k largest values is, by definition, the k-th largest. It sits on top of the heap, so reading the answer is a single Peek().
To add a value, put it in the heap. If the heap now holds k + 1 values, remove the smallest: it cannot be among the k largest. The heap never holds more than k + 1 values, so each push and pop costs O(log k), and the object stores only O(k) values.
Here is the heap for k = 3 as the starting numbers 4, 5, 8 and 2 go in one by one. Each line shows the heap's top and its size after the step:
after 4: top 4, count 1
after 5: top 4, count 2
after 8: top 4, count 3
after 2: top 4, count 3
The 2 went in and came straight out again, because it was the smallest of four values. The heap holds 4, 5 and 8, with 4 on top.
Step by step
- Declare the field
k, becauseAddneeds to know how many values to keep. - Declare
largest, aPriorityQueue<int, int>. C#'sPriorityQueue<TElement, TPriority>stores each element with a priority and returns the smallest priority first, so a plain one is already a min-heap. - In the constructor, store the parameter in the field with
this.k = k. The parameter and the field share a name, sothis.kis the field. - Loop over
numsand callAddfor each value. The starting numbers go through the same filter as every later one, and the constructor does not repeat the heap logic. The returned value is ignored: nobody has asked yet. - In
Add,Enqueuethe value with itself as its priority, so the heap orders the values themselves. - If
largest.Countis now more thank, callDequeue()to drop the smallest. - Return
largest.Peek(), the top of the heap.
The solution in C#
class KthLargest {
int k;
PriorityQueue<int, int> largest = new PriorityQueue<int, int>();
public KthLargest(int k, int[] nums) {
this.k = k;
foreach (int num in nums) {
Add(num);
}
}
public int Add(int val) {
largest.Enqueue(val, val);
if (largest.Count > k) {
largest.Dequeue();
}
return largest.Peek();
}
}
Two C# details matter here. First, PriorityQueue takes the element and its priority as two separate arguments, which is why val appears twice in Enqueue(val, val). Second, Dequeue() removes and returns the element with the smallest priority, while Peek() only reads it.
Run on the other two examples, the class gives -3 -2 -2 0 4 for k = 1, and 7 7 7 8 for k = 4 with starting numbers [7, 7, 7, 7, 8, 3] and the calls 2, 10, 9, 9.
Complexity
- Time: each
Adddoes oneEnqueueand at most oneDequeueon a heap of at most k + 1 values, so O(log k). The constructor makes oneAddper starting number, O(n log k) for n numbers. - Space: O(k), because the heap never holds more than k values after an
Addfinishes.
Edge cases and common mistakes
- Duplicates count separately. With
k = 4and the numbers 8, 7, 7, 7, 7, the 4th largest is a 7. A set would collapse the equal numbers and give the wrong answer. - Fewer than k starting numbers. The constructor may receive fewer than k numbers, or none at all. The heap then holds fewer than k values until enough have arrived. At least k numbers have arrived whenever
Addreturns, soPeek()is safe there. - A max-heap is the wrong tool. It puts the largest value on top, and you would have to pop k - 1 values to reach the k-th. The min-heap of size k puts the answer on top.
- Forgetting the field. Without storing
kin the class,Addcannot tell when to drop a value. - Sorting on every call. It passes small tests and runs out of time on large ones.
