Top K Frequent Elements

Top K Frequent Elements looks easy at first. Just count and pick the top ones, right? But the interviewer wants to see how you pick the top ones without sorting everything. That choice is the whole point of this question.

🎯 The Problem

You get an array of numbers and a number k, and return the most common numbers.

  • Return the k numbers that show up the most times.
  • The frequency of a number is how many times it appears.
  • A number that appears often belongs in the answer.
  • Assume the answer is always clear, so there is one correct set.

Let us say the array is [1, 1, 1, 2, 2, 3] and k is 2. The number 1 appears three times. The number 2 appears two times. The number 3 appears once. So the two most frequent numbers are 1 and 2.

Input: nums = [1, 1, 1, 2, 2, 3], k = 2
Output: [1, 2]
Explanation: 1 appears 3 times, 2 appears 2 times, 3 appears 1 time.
The top 2 by frequency are 1 and 2.

Here is the frequency picture of our example. Each number points to how many times it appears.

nums = 1,1,1,2,2,3

count each number

1 -> 3 times

2 -> 2 times

3 -> 1 time

pick top k = 1,2

🐒 Approach 1: Count Then Sort (Brute Force)

The idea in one line: count every number, then sort by count and take the first k.

The idea:

  • A hash map stores a key and a value and looks the key up almost instantly.
  • Build a map of each number to its count.
  • Sort all the numbers by count, high to low.
  • Take the first k.

Why it is weak:

  • Sorting orders every unique number, even the rare ones you discard.
  • That costs O(n log n), where n is how many unique numbers there are.
  • You only need the top k, not a full order.

Here is the count-then-sort code:

top_k_frequent_sorting.py
from collections import Counter
def top_k_frequent(nums, k):
counts = Counter(nums)
ordered = sorted(counts, key=lambda num: counts[num], reverse=True)
return ordered[:k]

βš–οΈ Approach 2: Count Then Heap of Size k (Better)

The idea in one line: keep only the k strongest counts in a heap, dropping the smallest as you go.

The idea:

  • A heap always keeps the smallest or largest item ready at the top.
  • Keep a heap of size k.

How it works:

  • Walk each number and its count.
  • Push it into the heap.
  • If the heap grows past k, remove the smallest.
  • At the end the heap holds the k highest counts.

Why it is better:

  • Each push or pop on a heap of size k costs O(log k).
  • The total is about O(n log k).
  • That beats O(n log n) when k is smaller than n.

Here is the heap-of-size-k code:

top_k_frequent_heap.py
from collections import Counter
import heapq
def top_k_frequent(nums, k):
heap = []
for num, freq in Counter(nums).items():
heapq.heappush(heap, (freq, num))
if len(heap) > k:
heapq.heappop(heap)
return [num for freq, num in heap]

⚑ Approach 3: Count Then Bucket Sort (Best)

The idea in one line: use the count itself as an index, so you read the top numbers in order with no sorting.

The idea:

  • A number can appear at most n times, where n is the array length.
  • So every count sits between 1 and n.
  • A bucket is a slot that holds all numbers sharing one count.

How it works:

  • Make empty buckets, one for each count from 0 to n.
  • Put each number into the bucket that matches its count.
  • Walk the buckets from the highest count down.
  • Collect numbers until you have k.

Why it is fast:

  • Count in one pass. Fill buckets in one pass. Read back in one pass.
  • No sorting at all. So the time is O(n).
  • That is as fast as it gets, since you must see every number once.

Here is the bucket layout for our example. Index is the count. The slot holds the numbers with that count.

index 0 -> empty

index 1 -> 3

index 2 -> 2

index 3 -> 1

index 4 -> empty

index 5 -> empty

index 6 -> empty

read from right: 1 then 2 -> answer 1,2

Steps to Solve

  1. Walk through the array and build a hash map of each number to its count.
  2. Make a list of empty buckets, one slot for each count from 0 up to the array length.
  3. For each number, put it in the bucket whose index equals its count.
  4. Walk the buckets from the highest index down to the lowest.
  5. Collect numbers from each bucket until you have gathered k of them.
  6. Return those k numbers.

This Python version uses a dictionary to count, then a list of buckets indexed by frequency.

top_k_frequent.py
def top_k_frequent(nums, k):
count = {} # number -> how many times
for num in nums:
count[num] = count.get(num, 0) + 1
n = len(nums)
buckets = [[] for _ in range(n + 1)] # buckets[freq] holds numbers
for num, freq in count.items():
buckets[freq].append(num) # place number by its frequency
result = []
for freq in range(n, 0, -1): # walk from highest count down
for num in buckets[freq]:
result.append(num)
if len(result) == k:
return result
return result
nums = [1, 1, 1, 2, 2, 3]
k = 2
print(top_k_frequent(nums, k))

The output of the above code will be:

[1, 2]

Let us walk through the Python version line by line, so you see why each part is there.

The line count = {} makes an empty dictionary. This will hold each number and how many times it shows up.

The loop for num in nums: goes over every number in the array. Inside, count[num] = count.get(num, 0) + 1 reads the current count for that number. The get(num, 0) part returns 0 if the number is new. Then we add one. So after this loop, count knows every number’s frequency.

The line buckets = [[] for _ in range(n + 1)] makes a list of empty lists. We need indexes from 0 to n, so we make n + 1 slots. Index freq will hold every number that appears freq times.

The loop for num, freq in count.items(): reads each number and its count. Then buckets[freq].append(num) drops the number into the slot that matches its count. So a number seen three times lands in buckets[3].

The loop for freq in range(n, 0, -1): walks the slots from the highest count down to 1. We want the most frequent numbers first, so we start from the top. Inside, we add each number to result. The moment len(result) == k, we return. So we stop as soon as we have enough.

⏱️ Time and Space Complexity

Counting is one pass, so that part is O(n). Sorting orders every unique number, so it adds O(n log n). The heap keeps only k items, so it costs O(n log k). The bucket method never sorts, so it stays at O(n). All of them need extra memory to hold the counts, which is O(n) space.

Approach Time Complexity Space Complexity
Count then sort O(n log n) O(n)
Count then heap of size k O(n log k) O(n)
Count then bucket sort O(n) O(n)

Tip

Mention sorting first. Then say a heap of size k is faster when k is small. Then land on bucket sort for the O(n) answer. Walking through all three shows the interviewer how your thinking improves step by step.

🧩 Key Takeaways

  • βœ… First count every number with a hash map. That part is always O(n).
  • βœ… Sorting the counts works but costs O(n log n), which is more than you need.
  • βœ… A heap of size k drops the cost to O(n log k) when k is small.
  • βœ… Bucket sort uses the count as an index, so it reaches O(n) time with no sorting.
  • βœ… Talk through all three approaches in an interview to show how you improve a solution.

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does the Top K Frequent Elements problem ask you to return?

    Why: It asks for the k numbers with the highest frequency, meaning the ones that appear the most times.

  2. 2

    Why is the bucket sort approach faster than sorting the counts?

    Why: Bucket sort places each number into a slot indexed by its count, so it reads results in order without any sorting step.

  3. 3

    What is the time complexity of using a heap of size k?

    Why: Each push or pop on a heap of size k costs O(log k), and we do it for n items, giving O(n log k).

  4. 4

    In the bucket sort solution, what does the index of each bucket represent?

    Why: Each bucket index is a frequency, so buckets[3] holds every number that appears exactly three times.

πŸš€ What’s Next?