347. Top K Frequent Elements - Explanation
Description
Given an integer array nums and an integer k, return the k most frequent elements within the array.
The test cases are generated such that the answer is always unique.
You may return the output in any order.
Example 1:
Input: nums = [1,2,2,3,3,3], k = 2
Output: [2,3]Example 2:
Input: nums = [7,7], k = 1
Output: [7]Constraints:
1 <= nums.length <= 10^4.-1000 <= nums[i] <= 10001 <= k <= number of distinct elements in nums.
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n) time and O(n) space, where n is the size of the input array.
Hint 1
A naive solution would be to count the frequency of each number and then sort the array based on each element’s frequency. After that, we would select the top k frequent elements. This would be an O(nlogn) solution. Though this solution is acceptable, can you think of a better way?
Hint 2
Can you think of an algorithm which involves grouping numbers based on their frequency?
Hint 3
Use the bucket sort algorithm to create n buckets, grouping numbers based on their frequencies from 1 to n. Then, pick the top k numbers from the buckets, starting from n down to 1.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Maps - Using dictionaries to count element frequencies efficiently
- Sorting - Sorting elements by custom criteria (frequency in this case)
- Heap/Priority Queue - Using min-heaps to maintain the top k elements efficiently
- Bucket Sort - Understanding how to use array indices as buckets for counting-based sorting
1. Sorting
Intuition
To find the k most frequent elements, we first need to know how often each number appears.
Once we count the frequencies, we can sort the unique numbers based on how many times they occur.
After sorting, the numbers with the highest frequencies will naturally appear at the end of the list.
By taking the last k entries, we get the k most frequent elements.
This approach is easy to reason about:
count the frequencies → sort by frequency → take the top k.
Algorithm
- Create a hash map to store the frequency of each number.
- Build a list of
[frequency, number]pairs from the map. - Sort this list in ascending order based on frequency.
- Create an empty result list.
- Repeatedly pop from the end of the sorted list (highest frequency) and append the number to the result.
- Stop when the result contains
kelements. - Return the result list.
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
count = {}
for num in nums:
count[num] = 1 + count.get(num, 0)
arr = []
for num, cnt in count.items():
arr.append([cnt, num])
arr.sort()
res = []
while len(res) < k:
res.append(arr.pop()[1])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
2. Min-Heap
Intuition
After counting how often each number appears, we want to efficiently keep track of only the k most frequent elements.
A min-heap is perfect for this because it always keeps the smallest element at the top.
By pushing (frequency, value) pairs into the heap and removing the smallest whenever the heap grows beyond size k, we ensure that the heap always contains the top k most frequent elements.
In the end, the heap holds exactly the k values with the highest frequencies.
Algorithm
- Build a frequency map that counts how many times each number appears.
- Create an empty min-heap.
- For each number in the frequency map:
- Push
(frequency, number)into the heap. - If the heap size becomes greater than
k, pop once to remove the smallest frequency.
- Push
- After processing all numbers, the heap contains the
kmost frequent elements. - Pop all elements from the heap and collect their numbers into the result list.
- Return the result.
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
count = {}
for num in nums:
count[num] = 1 + count.get(num, 0)
heap = []
for num in count.keys():
heapq.heappush(heap, (count[num], num))
if len(heap) > k:
heapq.heappop(heap)
res = []
for i in range(k):
res.append(heapq.heappop(heap)[1])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the array and is the number of top frequent elements.
3. Bucket Sort
Intuition
Each number in the array appears a certain number of times, and the maximum possible frequency is the length of the array.
We can use this idea by creating a list where the index represents a frequency, and at each index we store all numbers that appear exactly that many times.
For example:
- All numbers that appear
1time go into groupfreq[1]. - All numbers that appear
2times go into groupfreq[2]. - And so on.
After we build these groups, we look from the highest possible frequency down to the lowest and collect numbers from these groups until we have k of them.
This way, we directly jump to the most frequent numbers without sorting all the elements by frequency.
Algorithm
- Build a frequency map that counts how many times each number appears.
- Create a list of groups
freq, wherefreq[i]will store all numbers that appear exactlyitimes. - For each number and its frequency in the map, add the number to
freq[frequency]. - Initialize an empty result list.
- Loop from the largest possible frequency down to
1:- For each number in
freq[i], add it to the result list. - Once the result contains
knumbers, return it.
- For each number in
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
count = {}
freq = [[] for i in range(len(nums) + 1)]
for num in nums:
count[num] = 1 + count.get(num, 0)
for num, cnt in count.items():
freq[cnt].append(num)
res = []
for i in range(len(freq) - 1, 0, -1):
for num in freq[i]:
res.append(num)
if len(res) == k:
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Using a Max-Heap Instead of Min-Heap
When keeping track of the top k elements, a min-heap of size k is needed so you can efficiently remove the smallest frequency when the heap exceeds size k. Using a max-heap requires storing all elements and then extracting k times, which is less efficient. The min-heap approach maintains only the k largest frequencies at any time.
Forgetting to Handle Ties in Frequency
When multiple numbers have the same frequency, the order in which they appear in the result may vary. Most problem statements accept any valid ordering, but some solutions incorrectly assume a specific order or break when frequencies are equal. Ensure your comparison function handles equal frequencies gracefully.
Off-By-One in Bucket Sort Index
In bucket sort, frequencies range from 1 to n (the array length), so you need n + 1 buckets indexed 0 to n. A common mistake is creating only n buckets, causing an index out of bounds error when an element appears n times. Always allocate len(nums) + 1 buckets to accommodate all possible frequencies.
Sign in to join the discussion