How to recognise
the k largest, smallest, closest or most frequent items, or the k-th one, often from a long list or a stream.
How to solve problems like this
- Flip the heap: a min-heap for the k largest, a max-heap for the k smallest.
- Pick the key to compare: the value, a count from a hash map, the distance to a point.
- Fill the heap up to k items. After that, a new item replaces the root only if it beats it.
- At the end the heap holds the answer, and its root is the k-th best.
Variant: Merge k lists
The same, but the heap holds the first item of each of k sorted lists. The next item of the merged answer is always on top, so the k lists are not compared every time.
- Put one candidate per list in the heap: its first node, skipping empty lists. The heap compares nodes by node.val.
- Pop the smallest node and append it to the result.
- If the popped node has a next, push node.next: it becomes that list's candidate.
- The same shape finds the k-th smallest in a matrix with sorted rows (pop k times) and the smallest range that covers every list (also track the largest candidate).
Variant: Two heaps
The same, but with two heaps: one holds the smaller half of the numbers, the other the bigger half. The median is always on top of one of them, so nothing is re-sorted after each new number.
- Spot it: numbers arrive over time and you keep needing the middle, or any split into a lower and an upper part.
- Keep two heaps: a max-heap low for the smaller half and a min-heap high for the larger half.
- Add each number to low, then move the top of low to high. This keeps every value in low below every value in high.
- If high grew bigger than low, move its top back. Now low has the same size or one more.
- Read the answer from the tops: the top of low for an odd count, the average of both tops for an even one.
Where it's used
- Timers in Node.js. libuv, the library under Node.js, keeps timers in a heap: the one due first is always on top. So it never has to look through every timer to know how long to wait.
- Python. heapq.nlargest(k, data) keeps a heap of the best k and runs the rest of the data past it instead of sorting everything.
- RocksDB. RocksDB stores data in several sorted files. To read them in order, it merges them through a heap that always has the next key on top.