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. 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 has to handle this dual ordering correctly, which is what drives the data structure choices below.
1 <= words.length <= 500: With n at most 500, even an O(n^2) solution would pass. The follow-up still asks for O(n log k), so the goal is to reach that bound.1 <= words[i].length <= 10: Words are short, so a single string comparison costs at most 10 character comparisons. We treat each comparison as O(1) and leave the word length out of the complexity analysis.words[i] consists of lowercase English letters, so there are no unicode or special-character cases to handle.k is in [1, number of unique words], so k never exceeds the number of distinct words and the answer always has exactly k entries.Count every word's frequency, then sort the unique words using a comparator that handles both criteria: frequency descending first, and alphabetical order ascending to break ties. Take the first k words from the sorted list.
The comparator is the whole solution. Once the list is sorted by these two keys, the answer is the prefix of length k, and the output ordering is already correct because we sorted by the same keys the output requires.
freq mapping each word to its count.k words from the sorted list.Sorting orders all m unique words even though only k are needed. The next approach keeps a running set of only k candidates and discards the rest as it scans, which lowers the log factor from log m to log k.
A min-heap of size k holds the current top k candidates while we scan. Push each unique word into the heap, and when the heap grows past k, pop one word. After processing every word, the heap holds the top k.
The heap's comparator is the reverse of the output ordering. The result wants high frequency and, for ties, low alphabetical order. So the heap's minimum (the element that gets popped) must be the word that is worst by those criteria: lowest frequency, and for ties, highest alphabetical order.
The pop only ever removes the heap's minimum, which is the worst of the current k+1 candidates by frequency then alphabetical order. A word is discarded only when k strictly better words already sit in the heap, so no word that belongs in the final top k is ever removed. The heap yields elements from worst to best, so reversing the extraction order produces frequency-descending, alphabetical-ascending output.
words.The heap approach meets the O(n log k) follow-up. The next approach removes the log factor on the frequency dimension by grouping words directly into buckets indexed by frequency, leaving only the per-bucket alphabetical sort.
The maximum frequency any word can have is n, the case where every element is the same word. Create an array of n+1 buckets where bucket[i] holds all words with frequency i. Frequency is an integer in [1, n], so it serves directly as an array index without any comparisons. Walking from the highest bucket down to the lowest visits words in decreasing-frequency order.
Words sharing a bucket all have the same frequency, so they must come out in alphabetical order to satisfy the tie-breaking rule. Sorting each bucket on its own handles that. The combined size of all buckets is m unique words, so the total sorting cost stays bounded by O(m log m).
words.bucket[frequency].