Heaps
Use a heap when items arrive over time and each query needs the current smallest or largest item. If all values are known and processed once in order, compare against sorting first.
1. Choose the Heap by What Must Leave First
For a binary heap with n items, peek costs , push/pop , and bottom-up heapify . Storage is . The array is not sorted: only the root is guaranteed to be the extreme value.
| Task | Retained state | Root means |
|---|---|---|
Largest k values | Min-heap of at most k values | Worst retained candidate |
Smallest k values | Max-heap of at most k values | Worst retained candidate |
Merge m sorted lists | One pending item per nonempty list | Next global output |
| Running median | Max-heap below, min-heap above | The middle value(s) |
For an array implementation, children of index i are 2*i+1 and 2*i+2; the parent is (i-1)//2. Insertion sifts up; removal replaces the root with the last item and sifts down. Bottom-up heapify starts at the last parent: most nodes are near the leaves, so total repair work is linear. Use library operations unless implementation is the task.
2. Keep Only the Best K Candidates
After each input value, retain the largest k values seen so far. Adding one value can create only one excess candidate; removing the smallest restores the invariant. Duplicates count as separate values.
import heapq
def largest_k(values, k):
if k <= 0:
return []
kept = []
for value in values:
heapq.heappush(kept, value)
if len(kept) > k:
heapq.heappop(kept)
return kept
The result is unordered. For 1 <= k <= n, the root is the kth largest; sorting the retained values adds . Scanning takes time and space. Heapifying all values and popping k instead takes time and storage. Quickselect is another option for a single batch query; it does not maintain an answer as new items arrive.
In Top K Frequent Elements, count first, then keep (frequency, value) candidates. With U unique values, the map costs space even if the heap holds only k items.
3. Merge Sorted Streams and Track a Median
Multiway merge: for ascending lists, heapify (value, list_id, index) for each nonempty list's first item. Pop the smallest, emit it, and insert its successor from the same list. Each list's head is its smallest unseen value, so the smallest head is the next global value. With m lists and N total elements, full output takes time and auxiliary space. Stop after k outputs for time. Empty lists contribute nothing.
Running median: keep the lower half in a max-heap low and the upper half in a min-heap high. Every value in low must be <= every value in high, and len(high) must equal len(low) or exceed it by one. Insert relative to high's root, then move a root across if the sizes violate that rule. For odd size, the median is high's root; for even size, average the two roots. Insertion costs , lookup , and storage . Define the empty-stream result before reading either root.
4. Changing Priorities and Deleted Items
A heap does not provide efficient arbitrary lookup. In Delete Operations, pair each value with its original index and track which indices remain active. Index deletion updates that state; minimum deletion first pops stale roots, then removes the smallest active one. Rebuild the final result by original index to preserve order.
For changing priorities, keep (priority, id, version) entries and a map of current versions. Push an updated entry and discard obsolete versions when they reach the root. Do not mutate a priority inside the heap without repairing it. Across q inserted entries, each stale entry is removed once, giving total heap work; space can grow with updates, not just active items.
5. Key Takeaways
- For the largest
k, use a min-heap: its root is the candidate to evict, not the largest answer. - For sorted streams, store one head per stream instead of all remaining values.
- A median needs both the ordering and size invariants; balancing sizes alone is insufficient.
- Include frequency maps and stale entries in space analysis. Use
(priority, sequence_number, payload)when tied priorities must not compare payload objects.
Continue to Sorting for static selection, Greedy Algorithms when a proof determines which candidate to choose, and Stacks & Queues when arrival order determines processing order.
Practice Problems
Core Practice
Kth Largest Element in an Array
Given an integer array nums and an integer k, return the kth largest element in the array.
Merge Sorted Streams
Given m ascending integer arrays, return all their values in ascending order, preserving duplicates. Empty arrays and an empty collection are allowed.
Running Median
Implement add(value) and median() for a stream of integers. The median is the middle value for odd size and the average of the two middle values for even size. Querying an empty stream returns None in Python or (0, false) in Go.
Optional Variations (3)
Choose a variation when you want to explore a specific pattern. These are not required before moving to the next topic.
Top K Frequent Elements
Given an integer array nums and an integer k, return the k most frequent elements.
K Closest Points to Origin
Given an array of points where points[i] = [xi, yi] and an integer k, return the k closest points to the origin (0, 0).
Delete Operations
You're given an array of n integers, nums, and another array of at most n integers, operations, where each integer represents an *operation* to be performed on nums.