Problem
Given an integer array and a count k, return the k elements that appear most frequently. The problem guarantees that the k-th and (k+1)-th most frequent elements always have different frequencies, so there is exactly one valid answer.
- Input:
nums = [1, 1, 1, 2, 2, 3],k = 2 - Output:
[1, 2] - Explanation: 1 appears 3 times and 2 appears 2 times, making them the two most frequent elements.
Intuition
Every approach starts the same way โ build a frequency map in O(n). The question is how to extract the top k entries efficiently. Sorting the map by frequency works but costs O(n log n). A min-heap of size k improves this to O(n log k) by evicting the least-frequent element whenever the heap grows too large. The best approach exploits the fact that frequencies are bounded by n: use frequency as an array index (bucket sort), then scan from the highest index down โ no comparisons needed at all, just O(n).
Approach 1 โ Sorting
Count each element's frequency, sort the unique elements by frequency in descending order, and return the first k.
Steps:
- Build a frequency map over
nums. - Extract the unique elements into a list.
- Sort the list by frequency, highest first.
- Return the first
kelements.
1from collections import Counter
2
3def topKFrequent(nums: list[int], k: int) -> list[int]:
4 frequency = Counter(nums)
5 # sort unique elements by count descending; take the first k
6 return sorted(frequency.keys(), key=lambda num: -frequency[num])[:k]- Time: O(n log n) โ sorting the unique elements dominates; counting is O(n)
- Space: O(n) โ the frequency map and sorted list each scale linearly with input size
Approach 2 โ Min-Heap of Size k
Maintain a min-heap capped at size k, ordered by frequency with the least-frequent element on top. When the heap would exceed size k, evict the top โ discarding the element with the lowest frequency. Whatever survives in the heap at the end is the answer.
Steps:
- Build a frequency map over
nums. - Initialize an empty min-heap.
- For each unique element, push
(frequency, element)onto the heap. - If the heap size exceeds
k, pop the minimum โ this evicts the least-frequent element. - Return the elements remaining in the heap.
1import heapq
2from collections import Counter
3
4def topKFrequent(nums: list[int], k: int) -> list[int]:
5 frequency = Counter(nums)
6 min_heap = [] # (freq, num); Python heapq is a min-heap by default
7
8 for num, freq in frequency.items():
9 heapq.heappush(min_heap, (freq, num))
10 if len(min_heap) > k: # evict least frequent so heap stays at most size k
11 heapq.heappop(min_heap)
12
13 return [num for freq, num in min_heap]- Time: O(n log k) โ each of the n push/pop operations on the size-k heap costs O(log k)
- Space: O(n + k) โ O(n) for the frequency map and O(k) for the heap
Approach 3 โ Bucket Sort
An element's frequency is always between 1 and n. That bound lets us use frequency as an array index: create n + 1 buckets where buckets[i] holds every element that appears exactly i times. Scanning buckets from the highest index down gives elements in frequency order without any comparisons.
Steps:
- Build a frequency map over
nums. - Create
n + 1empty buckets (indices 0 through n). - For each unique element, append it to
buckets[frequency]. - Walk bucket indices from
ndown to 1, appending elements to the result. - Return once the result contains
kelements.
1from collections import Counter
2
3def topKFrequent(nums: list[int], k: int) -> list[int]:
4 frequency = Counter(nums)
5 # buckets[i] holds all elements appearing exactly i times; max freq = len(nums)
6 buckets: list[list[int]] = [[] for _ in range(len(nums) + 1)]
7
8 for num, freq in frequency.items():
9 buckets[freq].append(num)
10
11 result = []
12 for freq in range(len(buckets) - 1, 0, -1): # scan from highest frequency down
13 for num in buckets[freq]:
14 result.append(num)
15 if len(result) == k: # early exit the moment k elements are collected
16 return result
17
18 return result- Time: O(n) โ counting is O(n), filling buckets is O(n), scanning buckets is O(n) worst case
- Space: O(n) โ the frequency map and bucket array each scale linearly with input size
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Sorting | O(n log n) | O(n) | Quickest to write under time pressure; fine when k is close to n |
| Min-Heap (size k) | O(n log k) | O(n) | Better than sorting when k is much smaller than n |
| Bucket Sort | O(n) | O(n) | When optimal time is required and frequencies are bounded by input size |
Common Mistakes
- Using a max-heap over all elements instead of a min-heap of size k: pushing everything into a max-heap and popping k times is O(n log n) โ you've recreated sorting. The O(n log k) gain comes specifically from a min-heap capped at k that evicts as you insert.
- Bucket array size n instead of n + 1: when all elements are identical, frequency equals n, requiring bucket index n to be valid โ the array must have size
n + 1. - Placing the early-exit check in the outer loop instead of the inner: if you check
collected == konly after finishing an entire bucket, you can overshoot k when multiple elements share the same frequency. - Expecting the min-heap result to be frequency-ordered: the min-heap approach guarantees only that the k survivors are the top-k by frequency โ they are not sorted within that group. If the caller needs them ordered by frequency, sort after.
- Forgetting the unique-frequency guarantee: this problem ensures no tie between the k-th and (k+1)-th frequencies. Related problems like
top-k-frequent-wordsdrop that guarantee and require a lexicographic tiebreak, which changes the comparator.
Related Problems
top-k-frequent-wordsโ identical frequency counting pattern with an added lexicographic tiebreak for equal countskth-largest-element-in-an-arrayโ k-th selection using a heap or quickselectsort-characters-by-frequencyโ same frequency counting idea, but the output is the full string sorted by count rather than a top-k selectionfind-k-closest-elementsโ k-element selection with a proximity ordering instead of a frequency orderinglast-stone-weightโ max-heap manipulation on a dynamically changing collection