Kth Largest Element in a Stream (LC 703)
On this page
- Problem Statement
- 1. Algorithm & Pseudocode
- 2. Step-by-Step Analysis (Beginner-Friendly)
- 3. The Dry Run
- Phase A — Constructor (process initial nums in order)
- Phase B — add(3)
- Phase C — add(5)
- Phase D — add(10)
- Phase E — add(9)
- Phase F — add(4)
- 4. Java Solution
- Brute Force
- Optimal
- 5. The “Java vs. Others” Edge
- 6. Complexity Summary
Pattern: Two Heaps / Size-k Heap
Difficulty: Easy
Key Concept: Keep a min-heap of exactly k largest values seen so far; the heap’s minimum is the kth largest.
Problem Statement
Design a class KthLargest that finds the kth largest element in a stream of integers (elements arrive one at a time).
Constructor
KthLargest(int k, int[] nums)
k: which order statistic you want (1 = largest, 2 = second largest, …).nums: initial elements before anyaddcalls.
Method
int add(int val)
- Appends
valto the stream. - Returns the current kth largest element after including
val.
Constraints (typical)
1 <= k <= 10^40 <= nums.length <= 10^4-10^4 <= nums[i], val <= 10^4- At most
10^4calls toadd - It is guaranteed that there is always a valid answer when
addis called.
Input / Output (conceptual)
- Input:
k, initialnums, then a sequence ofadd(val)operations. - Output: After each
add, the kth largest value in all numbers seen so far (initial + all added values).
1. Algorithm & Pseudocode
Brute force (re-sort on every add)
maintain a dynamic list of all values (initial nums + each added val)
on add(val):
append val to the list
sort the list in descending order
return element at index (k - 1) // 0-based: kth largest
Optimal (min-heap of size k)
use a min-heap pq // smallest of the k largest sits at the root
helper shrink():
while pq.size() > k:
pq.poll() // drop the smallest among stored candidates
constructor(k, nums):
store k
for each x in nums:
pq.offer(x)
shrink()
// pq holds up to k elements: the k largest from nums (if enough elements exist)
add(val):
pq.offer(val)
shrink()
return pq.peek() // minimum in pq = kth largest overall
Why this works
If you keep exactly the k largest values in a min-heap, the smallest among those k is exactly the kth largest in the full multiset.
2. Step-by-Step Analysis (Beginner-Friendly)
-
Why not just sort once?
New values keep arriving. Any static sort becomes stale after the nextadd, so you either re-sort (expensive) or maintain a structure that stays correct incrementally. -
Why a heap of size k?
You do not need allnnumbers sorted. You only need enough information to know the kth largest. Keeping the top k values is sufficient; anything smaller than the kth largest can be forgotten. -
Why a min-heap (not a max-heap)?
Among theklargest values, the kth largest is the smallest of that group. A min-heap gives you that smallest in O(1) viapeek(). If you used a max-heap of sizek, the root would be the largest of the stream so far—not what you need. -
What does
poll()whensize > kdo?
After inserting a new value, you might havek + 1candidates in the heap. Removing the smallest of thosek + 1drops one value that cannot be in the topk, restoring the invariant: heap =klargest elements. -
Edge case: fewer than k numbers initially
Until the stream has at leastkelements, the heap may have fewer thankitems. Problem statements usually guarantee a valid kth largest by the time answers are required; still, in code you must only callpeek()when the heap is non-empty and matches problem guarantees. -
Class design
The constructor sets upkand seeds the heap fromnums. Eachaddupdates the structure and returns the answer—encapsulation hides the heap from callers.
3. The Dry Run
Parameters: k = 3, initial nums = [4, 5, 8, 2].
Convention: We use a min-heap; after each batch of operations we show heap contents (as a multiset; order inside the heap tree is not unique). We poll when size exceeds k.
Phase A — Constructor (process initial nums in order)
| Step | Action | Heap contents (multiset) | Size | After shrink (if > k) | peek (3rd largest so far) |
|---|---|---|---|---|---|
| A1 | offer 4 | {4} | 1 | — | 4 |
| A2 | offer 5 | {4,5} | 2 | — | 4 |
| A3 | offer 8 | {4,5,8} | 3 | — | 4 |
| A4 | offer 2 | {2,4,5,8} | 4 | poll 2 → {4,5,8} | 4 |
After construction, the 3rd largest among [4,5,8,2] is 4.
Phase B — add(3)
| Step | Action | Heap before | Size before | Heap after offer | Size | Shrink | Heap after | Return peek |
|---|---|---|---|---|---|---|---|---|
| B1 | offer 3 | {4,5,8} | 3 | {3,4,5,8} | 4 | poll 3 | {4,5,8} | 4 |
Phase C — add(5)
| Step | Action | Heap before | Offer | Size | Shrink (poll min) | Heap after | Return |
|---|---|---|---|---|---|---|---|
| C1 | add 5 | {4,5,8} | +5 | 4 | poll 4 | {5,5,8} | 5 |
(Among {4,5,8,5}, the three largest are 8,5,5; the smallest of those is 5.)
Phase D — add(10)
| Step | Heap before | After offer | Size | Poll | Heap after | Return |
|---|---|---|---|---|---|---|
| D1 | {5,5,8} | {5,5,8,10} | 4 | poll 5 | {5,8,10} | 5 |
Phase E — add(9)
| Step | Heap before | After offer | Size | Poll | Heap after | Return |
|---|---|---|---|---|---|---|
| E1 | {5,8,10} | {5,8,9,10} | 4 | poll 5 | {8,9,10} | 8 |
Phase F — add(4)
| Step | Heap before | After offer | Size | Poll | Heap after | Return |
|---|---|---|---|---|---|---|
| F1 | {8,9,10} | {4,8,9,10} | 4 | poll 4 | {8,9,10} | 8 |
Return value from each add (in order): 4, 5, 5, 8, 8. After construction, peek is 4 before any add; each row above shows the value returned by that add.
4. Java Solution
Brute Force
Idea: Keep an ArrayList, sort descending on every add, return index k - 1.
Time: O(m · (n + m) log(n + m)) over all operations in the worst case (each sort is O(N log N) where N grows with adds). Space: O(n + m) for stored values.
import java.util.*;
class KthLargestBruteForce {
private final int k;
private final List<Integer> data = new ArrayList<>();
public KthLargestBruteForce(int k, int[] nums) {
this.k = k;
for (int x : nums) {
data.add(x);
}
}
public int add(int val) {
data.add(val);
data.sort(Collections.reverseOrder());
return data.get(k - 1);
}
}
Optimal
Time: Each add is O(log k) for heap operations; constructor is O(n log k) for n = nums.length. Space: O(k).
import java.util.PriorityQueue;
class KthLargest {
private final int k;
private final PriorityQueue<Integer> pq = new PriorityQueue<>();
public KthLargest(int k, int[] nums) {
this.k = k;
for (int x : nums) {
pq.offer(x);
if (pq.size() > k) {
pq.poll();
}
}
}
public int add(int val) {
pq.offer(val);
if (pq.size() > k) {
pq.poll();
}
return pq.peek();
}
}
5. The “Java vs. Others” Edge
PriorityQueueis a min-heap by default in Java—ideal for this pattern. In C++,std::priority_queueis a max-heap by default, so you would usegreateror store negated values for the same logic—easy to invert by habit in an interview.- Class API: Java’s solution maps cleanly to LeetCode’s
KthLargest/adddesign; fieldskand the heap areprivate, exposing only the contract. - No
peekon empty: In production you would guardpeek(); LeetCode guarantees calls are valid. - Alternatives: You could use a fixed-size heap by batching, but the
offer+ conditionalpollpattern is the standard, readable approach.
6. Complexity Summary
| Approach | Time | Space | Notes |
|---|---|---|---|
| Brute Force | O((n + m) log(n + m)) per add in worst case |
O(n + m) |
Re-sorting the full list each time; simple but does not scale. |
| Optimal | O(n log k) init; O(log k) per add |
O(k) |
Min-heap of size k; peek gives kth largest. |
Here n = nums.length and m is the number of add calls.