Data Structures · Sorting & Heaps · A Field Guide

Top K frequent words

Count everything, then rank by two rules at once: most frequent first, ties broken alphabetically. The real algorithm lives in the comparator.

apple3
banana2
orange2
01 · Strip it down

Rank all unique words, return the first K

Given a list of words and a number K, return the K most frequent — with a tie-break: when two words appear equally often, the alphabetically smaller one ranks higher. So apple banana apple banana cat with K=3 gives apple, banana, cat (apple and banana tie at 2, apple wins the alphabet).

The ordering, in one phrase Frequency descending, then lexicographic order ascending. Every subtlety in this problem is just those two keys applied in the right direction.

02 · Two stages

Count first, then rank

The input repeats words, so stage one is a HashMap<String,Integer> built in one O(N) pass — the same counting move as the earlier frequency problems. What's new is stage two: once you have word → count, you must rank the unique words by the two-key rule and slice off the top K.

Stage 1 · score
word → frequency
One pass with a HashMap. O(N).
Stage 2 · rank
sort by comparator, take K
Frequency desc, then word asc.

03 · The heart of it

The comparator is the algorithm

Don't treat the comparator as implementation trivia — it encodes the definition of "better." Compare two words: if their counts differ, the higher count wins; if they tie, the alphabetically smaller wins. In Java, since sort is ascending by comparator result, you reverse the frequency comparison but leave the word comparison natural.

uniqueWords.sort((a, b) -> {
    int fa = freq.get(a), fb = freq.get(b);
    if (fa != fb) return Integer.compare(fb, fa);  // freq DESC
    return a.compareTo(b);                        // word ASC
});
The two directions that trip people up Frequency uses compare(fb, fa) (reversed → descending). The tie-break uses a.compareTo(b) (natural → ascending). Flip either and the ranking is wrong.

A leaderboard with a tiebreaker

Score ranks players; when two are level, a fixed rule (say, alphabetical name) breaks the tie. Frequency is the score; the alphabet is the tiebreaker.

Sorting a spreadsheet by two columns

Sort by "count" descending, then by "name" ascending. Multi-key sorting is exactly this — the comparator just chains the columns.

A bouncer keeping the best K

A heap of size K keeps only the top K seen so far — the weakest is at the door, evicted the moment someone better arrives. Handy when K ≪ total.


04 · Rank it live

Count, sort, slice at K

Type words (space-separated) and dial K. Every unique word is counted and ranked by the two-key rule; bars show frequency. The top K sit above the dashed cutoff in teal; everything below is dropped. Watch ties resolve alphabetically.

Word ranker

K = 3

Try dog cat bat dog cat bat (all tie → pure alphabetical) or z a z c b (one dominant word, then a three-way tie).


05 · Dry run

Where the tie-break earns its keep

For apple banana apple orange banana apple mango orange, K=3:

Rank Word Frequency Why here
1 apple 3 highest count
2 banana 2 tie at 2, banana < orange
3 orange 2 tie at 2, loses alphabet → but still in top 3
— mango 1 below cutoff
Input order is irrelevant Unlike "first unique element," ranking here depends only on counts and the alphabet — not arrival order. Don't output the first K words you happen to encounter; that ignores frequency entirely.

06 · The code

HashMap, then sort

import java.util.*;

static List<String> topKFrequent(String[] words, int k) {
    Map<String,Integer> freq = new HashMap<>();
    for (String w : words) freq.put(w, freq.getOrDefault(w, 0) + 1);

    List<String> uniq = new ArrayList<>(freq.keySet());
    uniq.sort((a, b) -> {
        int fa = freq.get(a), fb = freq.get(b);
        return fa != fb ? Integer.compare(fb, fa) : a.compareTo(b);
    });
    return new ArrayList<>(uniq.subList(0, k));
}

Counting is O(N); sorting the U unique words is O(U log U); total O(N + U log U), O(U) space. With N ≤ 1000 that's trivially fast — the simplest approach that comfortably fits the constraints is the right one. Java's declarative form works too: Comparator.comparingInt(freq::get).reversed().thenComparing(naturalOrder()).


07 · When "Top K" means heap

Keep only K, evict the worst

If unique words number in the millions but K is tiny, sorting everything is wasteful. Instead keep a size-K min-heap whose root is the worst currently-selected word. Each new word is offered; if the heap exceeds K, poll the root. That's O(U log K) — better when K ≪ U.

The heap comparator is the mirror image The heap must expose the worst word at the root, so its comparator is reversed from the output order: lower frequency first, and on a tie the alphabetically larger word first (b.compareTo(a)). After polling, reverse the collected list to get final order.
Approach Time Best when
Sort all unique O(N + U log U) simpler; fine for small N
Size-K min-heap O(N + U log K) K ≪ U (huge candidate set)

08 · Tools that don't fit

Why not a TreeMap, a set, or map order?

TreeMap by key

Sorts words alphabetically, but frequency is the primary key. zebra:10 must beat apple:1. Key-order alone can't express that.

A plain HashSet

Tells you which words exist, not how often. Ranking is frequency-first, so you need counts — a map, not a set.

HashMap iteration order

No order guarantee at all — not by frequency, not alphabetical. You must explicitly sort or use a configured heap.

First K encountered

Ignores frequency entirely. bus car car train → the answer is car, bus, not bus, car.


09 · The pattern

Frequency map, then whatever the question needs

This is the fourth question in the set built on a frequency map — and it shows the split cleanly. Stage one is always "what do I need to know about each distinct value?" Here: its count. Stage two is dictated by the exact wording:

"first"
preserve input order
first unique element
"every window"
sliding window
distinct-in-window, anagrams
"palindrome"
pair / parity arithmetic
longest palindrome
"top K"
sort or heap by a comparator
this problem

10 · One month from now

Reconstruct it, don't memorize it

  1. I need the K most frequent words → first I need every word's count.
  2. Build word → count with a HashMap in one pass.
  3. Now rank the unique words: higher frequency first.
  4. On a tie, alphabetically smaller first.
  5. Encode both rules in a comparator, sort, take the first K.
The transferable insight Top-K problems are two stages: score every candidate, then select/rank the best K by the exact tie-break rules. The comparator isn't a detail — it's the definition of "better," and getting its two directions right is the whole game.