Coding Interview Patterns Module 13 – Heaps and top-K
Top k frequent words: heap or buckets
The k most searched words of a log, ties in alphabetical order: count them, then pick the top k with a size-k heap or with buckets indexed by count.
What you will learn
- Find the k most frequent words of a log with a hash map and a heap of size k
- Find the k most frequent words with buckets indexed by count, and say when the buckets cost too much
- Apply an alphabetical tie rule with one order shared by the heap and the answer, in five languages
- Measure counting and the ways of picking the top k on logs of a million searches
- Reproduce three tie-breaking slips and catch them with a brute-force judge
Before you start
What you’ll learn · Before you start
What you will learn
- Find the k most frequent words of a log with a hash map and a heap of size k
- Find the k most frequent words with buckets indexed by count, and say when the buckets cost too much
- Apply an alphabetical tie rule with one order shared by the heap and the answer, in five languages
- Measure counting and the ways of picking the top k on logs of a million searches
- Reproduce three tie-breaking slips and catch them with a brute-force judge
Before you start
On this page
Top k frequent words asks for the k words that occur most often in a list, most frequent first, with a rule for words that occur equally often. It takes two steps: count every word with a hash map in one pass, then pick the k best (word, count) pairs, the top-k pattern of the previous lesson applied to counts. The tie rule is where answers go wrong. When equal counts are broken alphabetically, the order compares counts first and words second, and the picking step must follow exactly that order.
The problem: the most searched station codes
The problem
A railway enquiry app logs every station code that users search for, such as NDLS or SBC. Given the log log and
a number k, return the k most searched codes, most searched first; codes searched equally often appear in
alphabetical order. Limits: up to 10⁶ searches, each code 2 to 5 letters from A to Z, at most 10⁴ distinct codes,
and 1 ≤ k ≤ the number of distinct codes.
NDLS, SBC, NDLS, HWH, SBC, NDLS, MASwith k = 2 gives[NDLS, SBC]: three searches, then two.SBC, MAS, HWH, MAS, SBC, HWHwith k = 2 gives[HWH, MAS]: every code was searched twice, so alphabetical order alone decides, and SBC is left out.BCTwith k = 1 gives[BCT].
Counting comes first; the order rule then picks the k codes
Text description of the diagram
The figure shows the first two examples side by side, each as four boxes from top to bottom.
Left column, log 1:
- The log: NDLS, SBC, NDLS, HWH, SBC, NDLS, MAS.
- Counting gives NDLS 3, SBC 2, HWH 1 and MAS 1.
- The rule: higher count first, then alphabetical order.
- The answer for k = 2: NDLS, then SBC.
Right column, log 2:
- The log: SBC, MAS, HWH, MAS, SBC, HWH.
- Counting gives HWH 2, MAS 2 and SBC 2.
- All counts are equal, so alphabetical order alone decides.
- The answer for k = 2: HWH, then MAS. SBC is left out although it was searched as often, because it comes last alphabetically.
The brute force: one scan of the log per code
For each distinct code, scan the whole log and count it. Then sort the codes by count, highest first and alphabetically among equal counts, and return the first k. It is plainly right, so it can judge faster code, but its scans cost O(n · d) for n searches and d distinct codes. At 10⁶ searches and 10⁴ codes that is 10¹⁰ log entries read, about 100 seconds of C++ at 10⁸ simple steps a second.
# The brute force for the k most searched station codes: count each distinct code by scanning the whole log, then
# sort the distinct codes by count, highest first, and alphabetically among equal counts. The last line works out how
# many log entries those scans would read on a synthetic log of 200,000 searches over about 2,000 codes.
import random
import string
def most_searched_by_scanning(log, k):
counts = []
for code in sorted(set(log)):
seen = 0
for entry in log: # one full scan of the log per distinct code
if entry == code:
seen += 1
counts.append((code, seen))
counts.sort(key=lambda pair: (-pair[1], pair[0])) # higher count first, then alphabetical
return [code for code, _ in counts[:k]]
print(most_searched_by_scanning(["NDLS", "SBC", "NDLS", "HWH", "SBC", "NDLS", "MAS"], 2))
print(most_searched_by_scanning(["SBC", "MAS", "HWH", "MAS", "SBC", "HWH"], 2))
print(most_searched_by_scanning(["BCT"], 1))
rng = random.Random(9)
codes = ["".join(rng.choices(string.ascii_uppercase, k=4)) for _ in range(2_000)]
log = [rng.choice(codes) for _ in range(200_000)]
distinct = len(set(log))
print(f"{len(log):,} searches of {distinct:,} codes: one scan per code would read {len(log) * distinct:,} log entries") Output
['NDLS', 'SBC'] ['HWH', 'MAS'] ['BCT'] 200,000 searches of 1,996 codes: one scan per code would read 399,200,000 log entries
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 scan_each.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
On the synthetic log of 200,000 searches, one scan per code would read 399,200,000 entries, about 2,000 times the length of the log itself.
What to notice
The scans count each code separately, and one pass with a hash map from code to count does it for all of them, as in the lesson on frequency maps. After counting, the question is a top-k over d (code, count) pairs, ordered by count first and then alphabetically. Three ways pick those pairs:
- Count and sort sorts all d pairs by that order and keeps the first k, in O(d log d) time.
- A size-k heap keeps the k best pairs, with the worst of them on top, in O(d log k) time.
- Buckets by count list the codes of each count in an array indexed by count, then walk it from the top.
Counting in one pass, then a top-k over the pairs Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The size-k heap, with the worst pair on top
- Why the k best pairs survive, proved with an invariant
- A dry run on the tie example, k = 2
- The code in five languages
- Buckets indexed by count
- Checked against a judge on 3,000 logs
- Measured: counting, sorting, the heap and the buckets on a million searches
- Three tie slips, reproduced
- Edge cases and common bugs
- Exercise: the k most frequent words
- The k most frequent words, ties in alphabetical order
- Variants: numbers, sparse buckets, per region and the last hour
- A practice set with sample answers
- Check yourself
- Interview questions
- In the k most frequent words problem with ties broken alphabetically, why does the order need two keys, and which comes first?
- You need the 100 most frequent words of 10⁸ searches with about 10⁶ distinct words, ties broken alphabetically. Compare counting and sorting, a size-k heap and buckets by count. Which do you choose, and what would change your mind?
- A search box shows the 10 most searched words of the last hour, refreshed every second, at about 2,000 searches a second. Design the data structures, and give the cost of handling one search and of one refresh.
- A solution returns [w for w, _ in Counter(words).most_common(k)] and fails a hidden test of the alphabetical tie rule. Explain the failure, give the smallest input that shows it, and fix it with as few changes as possible.
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- collections.Counter (most_common and the order of equal counts) (Python Software Foundation)
- heapq: Heap queue algorithm (max-heap functions, nsmallest) (Python Software Foundation)
- Sorting Techniques: sort stability and complex sorts (Python HOWTOs) (Python Software Foundation)
- Java SE 25 API: PriorityQueue (Oracle)
- Java SE 25 API: Comparator (comparingInt, thenComparing, reverseOrder) (Oracle)
- Java SE 25 API: String.compareTo (Oracle)
- std::priority_queue (cppreference.com)
- Package container/heap (The Go Authors)
- The Go Programming Language Specification: For statements with range clause (The Go Authors)
- ECMAScript 2026 Language Specification: Array.prototype.toSorted (Ecma International)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress