Hello! In our last lesson, we constructed a MinHeap class from scratch, giving us a powerful tool for efficiently managing ordered data. We saw how the add and poll operations work in time by maintaining the heap's structural and ordering properties.
Today, we'll put that MinHeap to work and solve a classic interview problem. This lesson focuses on a fundamental algorithmic pattern: using a bounded heap to find the k largest or smallest items in a collection or from a continuous stream of data. Mastering this pattern is a significant step towards interview readiness, as it demonstrates a non-obvious and highly efficient use of a core data structure.
Our goal is to solve the "Kth Largest Element in a Stream" problem. This will not only solidify your understanding of heap operations but also show you how to choose the right tool to dramatically improve an algorithm's performance.
The Problem: Finding the Kth Largest Element
Imagine you're processing a real-time feed of stock prices, user scores, or sensor readings. A common requirement is to constantly know the k-th largest value seen so far. For example, what is the 10th highest price in the last hour?
Let's formalize this as an interview problem, "Kth Largest Element in a Stream" (similar to LeetCode #703):
- You are given an integer
kand an initial stream of numbers. - You need to implement a class that supports an
addmethod. - Each time
add(val)is called, it should incorporate the new valuevalinto the stream and return the currentk-th largest element.
A simple, but inefficient, approach would be to store all the numbers in a list, add the new number, sort the list in descending order, and pick the element at index k-1. If you have N numbers, each add operation would take time. As the stream grows, this becomes prohibitively slow. We need a much smarter way.
The Bounded Min-Heap: An Elegant Solution
The key insight is that we don't need to store all the numbers. We only care about the k largest ones. Any number that isn't in this top k group is irrelevant to our answer.
This leads to a counter-intuitive but powerful idea: to find the k largest elements, we will use a min-heap of size k.
Why a min-heap?
- A min-heap gives us instant (
O(1)) access to its smallest element (the root). - If we maintain a min-heap that only contains the k largest elements seen so far, then the root of this heap is, by definition, the smallest of the largest—which is exactly the
k-th largest element overall.
Let's make this concrete. If k=5 and our min-heap holds the 5 largest numbers seen so far, say {90, 95, 101, 105, 110}, the heap's root will be 90. This 90 is our 5th largest element. Any new number smaller than 90 can be ignored, as it won't change the top 5. If a new number like 103 arrives, it belongs in the top 5, and 90 gets kicked out. The min-heap is the perfect data structure to manage this "kicking out" process efficiently.
The following resource provides an excellent explanation of this intuition.
703. Kth Largest Element in a Stream - In-Depth Explanation
This article from Algo.monster clearly explains the reasoning behind choosing a min-heap for this problem.
Focus on the section How We Pick the Algorithm. It does a great job of explaining why a min-heap of size k is the ideal structure.
The Algorithm in Action
Now that we have the core idea, let's detail the algorithm for the add method, assuming we have our MinHeap class from the previous lesson.
- Add the new element: Push the new value
valinto the min-heap. - Maintain the bound: If the heap's size is now greater than
k, remove the smallest element by callingpoll(). - Return the answer: The
k-th largest element is now at the root of the heap, so we can return it withpeek().
This simple two-step process (add, then poll if oversized) ensures the heap never stores more than k elements and that those k elements are always the largest ones encountered.
The NeetCode video below walks through this exact logic. It uses Python, but the concepts are universal and map directly to our TypeScript implementation.
Kth Largest Element in a Stream - Leetcode 703 - Python
This video by NeetCode is a classic resource for this problem. It visualizes the process and explains the logic clearly.
First, watch the segment that explains why we use a min-heap of size k. Then, jump to the part explaining the add function logic, which shows how to handle a new incoming number.
TypeScript Implementation
Let's translate this logic into a KthLargest class in TypeScript. This class will use the MinHeap we built in the previous lesson.
// Assuming the MinHeap class from the previous lesson is available
// class MinHeap {
// public add(value: number): void { ... }
// public poll(): number | null { ... }
// public peek(): number | null { ... }
// public size(): number { ... }
// }
class KthLargest {
private k: number;
private minHeap: MinHeap;
constructor(k: number, nums: number[]) {
this.k = k;
this.minHeap = new MinHeap();
// Process the initial stream of numbers
for (const num of nums) {
this.add(num);
}
}
public add(val: number): number | null {
// If the heap has less than k elements, it's not full yet.
// Or, if the new value is larger than the smallest of the top k elements (the heap's root),
// then this new value belongs in our set of top k elements.
if (this.minHeap.size() < this.k || val > this.minHeap.peek()!) {
this.minHeap.add(val);
}
// If adding the new element made the heap too large,
// remove the smallest element to maintain the size k bound.
if (this.minHeap.size() > this.k) {
this.minHeap.poll();
}
// The root of the heap is the kth largest element.
// The problem often guarantees we can return a valid number,
// but it's good practice to handle the case where the heap isn't full yet.
if (this.minHeap.size() < this.k) {
return null; // Or handle as per problem spec
}
return this.minHeap.peek();
}
}
A slightly more concise way to write the add method, which is often seen in solutions, is to always add the element and then immediately trim the heap if it exceeds size k.
public add(val: number): number | null {
// 1. Add the new value unconditionally.
this.minHeap.add(val);
// 2. If the heap is now larger than k, remove the smallest element.
if (this.minHeap.size() > this.k) {
this.minHeap.poll();
}
// 3. Return the root, which is the k-th largest element.
if (this.minHeap.size() < this.k) {
return null;
}
return this.minHeap.peek();
}
Both implementations are correct and achieve the same result. The second one is just a bit more direct.
The Algo.monster resource provides a complete implementation using a library MinPriorityQueue, but the logic is identical to what we've written.
703. Kth Largest Element in a Stream - In-Depth Explanation
Let's look at the implementation details and the final code.
Read the sections on Implementation Details and review the TypeScript code. Notice how it directly maps to the logic we just discussed: enqueue (add), check size, and dequeue (poll) if needed.
Complexity Analysis
Let's analyze the efficiency of our bounded heap approach.
- Space Complexity: We only ever store
kelements in our heap, regardless of how many numbers we process. The space complexity is therefore . - Time Complexity:
- Constructor: We process the initial
Nnumbers. For each number, we perform anaddoperation on a heap of size at mostk. This takes time. The total time for initialization is . addmethod: Each call involves oneadd() and possibly onepoll(). The total time per call is .
- Constructor: We process the initial
This is a huge improvement over the time per addition of the naive sorting approach.
Generalizing the Pattern
This bounded heap pattern is not limited to just one problem. It's a general technique for finding the "Top K" of anything. Consider another famous problem: "Top K Frequent Elements" (LeetCode #347).
The goal is to find the k most frequent elements in an array. The solution uses the same pattern:
- Use a
Mapto count the frequency of each number. - Create a min-heap of size
kthat stores[element, frequency]pairs. The heap should be ordered by frequency. - Iterate through your frequency map. For each
[element, frequency]pair, add it to the heap. If the heap size exceedsk,poll()the pair with the lowest frequency. - After iterating through all elements, the heap will contain the
kmost frequent elements.
This shows the power of recognizing patterns. Once you understand the bounded heap, you can apply it to a whole family of problems.
347. Top K Frequent Elements - Solution & Explanation
This article from NeetCode explains the solution to a related problem using the same pattern.
Read the Intuition section to see how the bounded min-heap idea is applied to frequencies. Also, the "Common Pitfalls" section has a great summary titled Using a Max-Heap Instead of Min-Heap that reinforces why our counter-intuitive choice is the right one.
Conclusion
Today we explored the bounded heap pattern, a crucial technique for solving "Top K" style problems efficiently. This is a common pattern in coding interviews because it requires you to think beyond the obvious solution and apply the right data structure for the job.
Key Takeaways:
- Bounded Heap Pattern: To find the
klargest (or smallest) items from a large collection, maintain a heap of sizek. - Min-Heap for K Largest: Use a min-heap of size
kto track theklargest elements. The root of the heap is always thek-th largest element. - Max-Heap for K Smallest: Conversely, you would use a max-heap of size
kto find theksmallest elements. - Efficiency: This pattern offers excellent performance, with time per addition and space, making it ideal for processing large streams of data.
We have now concluded our module on trees and heaps. In the next module, we will venture into an even more general and powerful data structure: graphs. We will start by learning how to represent relationships between entities as a graph and then explore fundamental traversal algorithms like Breadth-First Search (BFS) and Depth-First Search (DFS). The heap we've studied will make a return appearance in one of the most famous graph algorithms, Dijkstra's algorithm for finding shortest paths.
Can't find a good explanation? Sign up and we'll make it for you
Sign up