Pattern 09 of 20

Heap / Priority Queue

Always know the smallest or largest item: top-k, merging streams, scheduling.

When to use it

  • You need the k largest, smallest or most frequent items
  • Merging several sorted sequences
  • Repeatedly taking the best next option (the cheapest, the earliest)

The idea

A heap gives the minimum (or maximum) in O(1) and accepts or removes items in O(log n). Keeping a heap of size k instead of sorting everything is the classic win.

Typical cost O(n log k) time, O(k) space.

How it works

A heap keeps one extreme element (the smallest or the largest) available in O(1), while inserting and removing cost O(log n). It never sorts everything; it only maintains enough order to know what comes next. That is cheaper than sorting when you only need the next few items.

For top-k, the counterintuitive move is to keep a min-heap of size k. Each new item competes with the weakest of the current top k, which sits at the root. The heap never grows past k, so the cost is O(n log k) instead of O(n log n).

Heaps also merge many sorted sources. Seed the heap with the first element of each list; every time you pop the smallest, push the next element from the same list. The heap holds at most one candidate per source.

In disguise, a heap appears in scheduling (always run the task that finishes first), in simulations (repeatedly take the two largest stones), in running medians (two heaps balanced around the middle), and inside Dijkstra's algorithm.

How to attack it

  1. 1Ask whether you repeatedly need the current minimum or maximum of a changing set.
  2. 2Pick min-heap or max-heap, and the size bound if it is a top-k question.
  3. 3Decide what to store: a value, or a tuple with a priority first and a tie-breaker.
  4. 4Write the push and pop loop, and what triggers each.
  5. 5Compare with sorting once: if the set never changes, a sort may be simpler.

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

import heapq


def k_largest(nums, k):
    # Min-heap of size k: the root is the weakest of the current top k.
    heap = []
    for x in nums:
        heapq.heappush(heap, x)
        if len(heap) > k:
            heapq.heappop(heap)    # drop the smallest
    return sorted(heap, reverse=True)


def merge_sorted(lists):
    heap = []
    for i, lst in enumerate(lists):
        if lst:
            # (value, list index, position): the index breaks ties safely.
            heapq.heappush(heap, (lst[0], i, 0))
    out = []
    while heap:
        val, i, j = heapq.heappop(heap)
        out.append(val)
        if j + 1 < len(lists[i]):
            heapq.heappush(heap, (lists[i][j + 1], i, j + 1))
    return out

# Python only has a min-heap: push -x to simulate a max-heap.

Variants

Top k with a bounded heap
A min-heap of size k for the k largest (or a max-heap for the k smallest); O(n log k).
K-way merge
One candidate per sorted source in the heap; pop the smallest and push its successor.
Two heaps for a median
A max-heap for the lower half and a min-heap for the upper half, rebalanced so their sizes differ by at most one.
Greedy scheduling
Sort events by start time and keep a heap of end times or of available work, always taking the best next option.
Lazy deletion
Instead of removing arbitrary items, mark them stale and skip them when they reach the top.

Pitfalls

  • Using a max-heap of all n items for top-k, which costs O(n log n) and O(n) memory.
  • Pushing tuples whose second element is not comparable (Python compares tuples element by element on ties).
  • Forgetting that Python's heapq is a min-heap and not negating values for max-heap behavior.
  • Assuming the heap's underlying array is sorted; only the root is guaranteed.
  • Sorting the JavaScript array on every insert instead of writing or using a real heap.

What it costs

OperationCost
Peek min / maxO(1)
Push / popO(log n)
Heapify an arrayO(n)
Top k of n itemsO(n log k) time, O(k) space
Merge k sorted lists, N items totalO(N log k)

30 Heap / Priority Queue problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1K-th Loudest ReadingFreeEasy
  2. 2Boulder CrusherFreeEasy
  3. 3Closest Readings to a TargetFreeEasy
  4. 4K-th Score LeaderboardEasy
  5. 5Cable Splicing CostEasy
  6. 6Weakest SquadsEasy
  7. 7Shrinking Treasure PilesEasy
  8. 8Almost Sorted RestoreEasy
  9. 9Min-Heap From ScratchEasy
  10. 10Most Common TagsMedium
  11. 11Nearest DronesMedium
  12. 12Cooldown SchedulerMedium
  13. 13Merge Sorted FeedsMedium
  14. 14Lightest K PairsMedium
  15. 15Single-Core Job RunnerMedium
  16. 16Rooftop RunnerMedium
  17. 17N-th Number Built From PrimesMedium
  18. 18Seat Reservation DeskMedium
  19. 19Fewest Conference RoomsMedium
  20. 20Exam Pass BoostMedium
  21. 21Best Crew ScoreMedium
  22. 22Server Task AssignmentMedium
  23. 23Hiring From Both EndsMedium
  24. 24Busiest RoomMedium
  25. 25Median BoardHard
  26. 26Seed Capital ProjectsHard
  27. 27Smallest Covering RangeHard
  28. 28Rainwater on TerrainHard
  29. 29Wage Group CostHard
  30. 30Window MediansHard

Solve them in the browser, in Python or JavaScript.