Last Updated: March 29, 2026
We have a list of words, some of which repeat, and we need to find the k most frequently occurring ones. So far this sounds identical to "Top K Frequent Elements," but there's a twist: when two words share the same frequency, they must be ordered alphabetically. The output must also be sorted by frequency (highest first), not returned in any order.
This tie-breaking requirement changes the problem significantly. With numbers, you could return results in any order. Here, the custom sort has two keys: frequency (descending) and then lexicographic order (ascending). Every approach needs to handle this dual ordering correctly, which makes data structure choices more interesting.
1 <= words.length <= 500 — With n at most 500, even O(n^2) solutions are fast enough. But the follow-up asks for O(n log k), so we should aim higher.1 <= words[i].length <= 10 — Words are short. String comparisons are effectively O(1) since they're bounded by 10 characters.words[i] consists of lowercase English letters — No unicode or special character edge cases to worry about.k is valid — We won't need to handle k larger than the number of unique words.The most direct approach: count every word's frequency, then sort the unique words using a comparator that handles both criteria. Sort primarily by frequency in descending order, and break ties by alphabetical order ascending. Then grab the first k words.
This is the first thing most people think of, and honestly, for n up to 500, it runs in well under a millisecond. The logic is clean and easy to verify.
freq mapping each word to its count.k words from the sorted list.This approach sorts all unique words when we only need the top k. What if we maintained only k candidates as we scanned, discarding the rest immediately?
Instead of sorting everything, we can use a min-heap of size k. The idea: scan through each unique word and push it into the heap. When the heap grows larger than k, we pop the "least desirable" word. After processing all words, the heap holds exactly the top k.
The comparator for the min-heap needs to be the reverse of what we want in the final output. We want high frequency and low alphabetical order in our result. So the heap should evict the word that's "worst" by our criteria: lowest frequency, and for ties, highest alphabetical order (since alphabetically later words are less desirable).
The min-heap acts as a sliding window over the candidates, always keeping the k best. By defining the heap's minimum as the least desirable word among the top k, we ensure that whenever a better word arrives and pushes the heap over size k, the evicted word is exactly the one we'd want to drop. After extraction, we reverse because the heap naturally yields elements from least-to-most desirable.
words.The heap approach meets the follow-up requirement. But what if we could skip comparison-based ordering entirely by grouping words directly by frequency?
The maximum frequency any word can have is n (if every element is the same word). So we create an array of n+1 buckets, where bucket[i] holds all words with frequency i. Then we walk from the highest bucket down, collecting words until we have k.
Within each bucket, words need to be in alphabetical order to satisfy the tie-breaking rule. We can sort each bucket individually. Since bucket sizes are small in practice, this adds minimal overhead.
Bucket sort sidesteps comparison-based sorting by using frequency as an array index. Since frequency is always an integer between 1 and n, we can create a direct-address table. Walking from the highest bucket downward gives us words in decreasing frequency order. Sorting within each bucket handles the alphabetical tie-breaking.
words.bucket[frequency].