278. Top K Frequent Elements
๐ LeetCode Problem: 347. Top K Frequent Elements
๐ Difficulty: Medium
๐ท๏ธ Topics: Array, Hash Table, Divide and Conquer, Sorting, Heap (Priority Queue), Bucket Sort, Counting, Quickselect
Problem Statement
Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
Constraints:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- k is in the range [1, the number of unique elements in the array]
- It is guaranteed that the answer is unique
Follow up: Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
๐ Understanding the Problem
What are we finding?
Input: [1,1,1,2,2,3]
Frequencies:
1 appears 3 times
2 appears 2 times
3 appears 1 time
k = 2 (want 2 most frequent)
Sorted by frequency (descending):
1 (freq=3) โ most frequent
2 (freq=2) โ 2nd most frequent
3 (freq=1)
Answer: [1, 2]
Key Observations:
1. Need to COUNT frequencies
โ Use HashMap: number โ frequency
2. Need TOP K by frequency
โ Sort? Heap? Bucket sort?
3. Order doesn't matter in result
โ [1,2] and [2,1] both correct
โ This gives us flexibility!
4. Follow-up wants better than O(n log n)
โ Sorting is O(n log n)
โ Can we do O(n)?
๐ The Natural Thinking Process
When you first see this problem:
Question: Find k most frequent elements
First Thought: "Count frequencies, then sort by frequency"
Step 1: Count with HashMap
Step 2: Sort by frequency
Step 3: Take top k
Is this optimal? NO (O(n log n)), but it WORKS!
Let's try it first.
The Evolution:
BRUTE FORCE THINKING:
"Count frequencies โ Sort by count โ Take k"
Time: O(n log n) for sorting
Problem: Follow-up wants better!
โฌ
REALIZATION 1:
"I don't need FULL sort, just TOP K!"
"Similar to Kth Largest Element problem!"
"Use MIN-HEAP of size k!"
โฌ
OPTIMIZATION 1: Min-Heap
"Keep heap of size k with k most frequent"
"Min-heap evicts least frequent easily"
Time: O(n log k) - better when k << n!
But follow-up wants O(n)...
โฌ
REALIZATION 2:
"Frequency is bounded! Max freq = n"
"What if I create buckets by frequency?"
"Bucket i contains elements with frequency i"
โฌ
OPTIMIZATION 2: Bucket Sort
"Create n+1 buckets (freq 0 to n)"
"Put each element in its frequency bucket"
"Collect from high frequency to low"
Time: O(n) - OPTIMAL! โจ
๐ฏ Approach 1: Brute Force - Sort by Frequency โญ
The First Natural Solution
The Thought Process:
Step 1: Read problem
"Find k most frequent elements"
Step 2: How to find frequency?
"Count with HashMap"
Key: number, Value: frequency
Step 3: How to find most frequent?
"Sort by frequency (descending)"
"Take first k elements"
This is O(n log n), but let's code it first!
Visual Tracking - Complete Example
Input: nums = [1,1,1,2,2,3], k = 2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 1: Count Frequencies
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Process array element by element:
nums[0] = 1: map = {1: 1}
nums[1] = 1: map = {1: 2}
nums[2] = 1: map = {1: 3}
nums[3] = 2: map = {1: 3, 2: 1}
nums[4] = 2: map = {1: 3, 2: 2}
nums[5] = 3: map = {1: 3, 2: 2, 3: 1}
Final frequency map:
1 โ 3
2 โ 2
3 โ 1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 2: Create List of (number, frequency) Pairs
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
From map, create list:
[(1, 3), (2, 2), (3, 1)]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 3: Sort by Frequency (Descending)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Sort by frequency (second element of pair):
Before sort: [(1, 3), (2, 2), (3, 1)]
After sort: [(1, 3), (2, 2), (3, 1)]
โ freq=3 โ freq=2 โ freq=1
highest lowest
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 4: Take Top K Elements
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
k = 2
Take first 2 elements:
(1, 3) โ number is 1
(2, 2) โ number is 2
Result: [1, 2] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESULT
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Top 2 frequent elements: [1, 2]
Verification:
1 appears 3 times โ most frequent โ
2 appears 2 times โ 2nd most frequent โ
3 appears 1 time โ not in top 2
Implementation
import java.util.*;
/**
* Brute Force: Sort by frequency
* Time: O(n log n)
* Space: O(n)
*/
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// Step 1: Count frequencies
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Step 2: Create list of (number, frequency) pairs
List<int[]> pairs = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
pairs.add(new int[]{entry.getKey(), entry.getValue()});
}
// Step 3: Sort by frequency (descending)
pairs.sort((a, b) -> b[1] - a[1]);
// Step 4: Take top k
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = pairs.get(i)[0];
}
return result;
}
}
โฐ Time: O(n log n) - Sorting dominates
๐พ Space: O(n) - HashMap + List
Why This Works but is Slow:
โ Simple and straightforward
โ Easy to code and understand
โ Handles all cases correctly
โ O(n log n) doesn't meet follow-up requirement
โ Sorting entire list when we only need k elements
โ Wasteful when k << unique elements
๐ก The First AHA Moment
PROBLEM ANALYSIS:
"I'm sorting ALL elements by frequency"
"But I only need TOP K!"
This is EXACTLY like "Kth Largest Element" problem!
INSIGHT:
"Don't need full sort for top K!"
"Use HEAP to track top K efficiently!"
Similar to Problem 275 (Kth Largest):
- That used max-heap
- This uses min-heap of size k
Keep k most frequent elements in heap
Min-heap makes evicting least frequent easy!
TIME IMPROVEMENT:
Full sort: O(n log n)
Min-heap: O(n log k)
When k << n, MUCH better!
Example: n=10000, k=10 โ 10000รlog(10) vs 10000รlog(10000)
๐ฏ Approach 2: Min-Heap of Size K โญโญ
The Better Solution
The Evolution of Thought:
Brute Force: Sort all โ Take k
โฌ
Question: "Do I need FULL sort for just k elements?"
โฌ
Answer: "NO! Use min-heap like in Kth Largest!"
โฌ
Better Idea: "Keep heap of size k with k most frequent!"
WHY Min-Heap and Not Max-Heap? - Critical Reasoning
The Core Question:
We want TOP K most frequent elements.
Should we use Min-Heap or Max-Heap? Why?
Answer Through Reasoning:
APPROACH 1: Max-Heap (seems natural?)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Max-heap keeps LARGEST on top.
If we use max-heap:
- Add all elements to heap
- Poll k times to get top k
Code:
PriorityQueue<Integer> maxHeap =
new PriorityQueue<>((a, b) -> freqMap.get(b) - freqMap.get(a));
for (int num : freqMap.keySet()) {
maxHeap.offer(num); // Add all
}
for (int i = 0; i < k; i++) {
result[i] = maxHeap.poll(); // Get top k
}
Analysis:
- Add n unique elements: O(n log n)
- Poll k elements: O(k log n)
- Total: O(n log n)
Problem: STILL O(n log n)! โ
Not better than sorting!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
APPROACH 2: Min-Heap of Size K (smart!)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Min-heap keeps SMALLEST on top.
Strategy:
- Maintain heap of ONLY k elements
- Heap contains k MOST frequent
- Min on top = LEAST frequent among top k
- Easy to evict when found more frequent!
Why this works:
If heap has k elements and we find element with higher freq:
- Current min freq in heap = least frequent of top k
- New element has higher freq
- Remove min (least of top k)
- Add new element
- Heap still has k most frequent!
Code:
PriorityQueue<Integer> minHeap =
new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));
for (int num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll(); // Remove least frequent
}
}
Analysis:
- Process n unique elements
- Each offer: O(log k)
- Each poll: O(log k)
- Total: O(n log k)
When k << n: MUCH better! โ
Example: k=10, n=10000 โ log(10) << log(10000)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WHY MIN-HEAP WINS:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Difference:
Max-heap: Must add ALL elements โ O(n log n)
Min-heap: Maintain ONLY k elements โ O(n log k)
The Insight:
We DON'T need all elements sorted!
We ONLY need top k!
Min-heap of size k gives us:
- k most frequent elements (in heap)
- Easy eviction (poll removes least of top k)
- O(log k) operations, not O(log n)!
This is the SAME pattern as Problem 275 (Kth Largest)! โจ
Visual Tracking - Complete Example
Input: nums = [1,1,1,2,2,3,4,4,4,4], k = 2
Step 1: Count frequencies
freqMap = {1: 3, 2: 2, 3: 1, 4: 4}
Step 2: Build min-heap of size k=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Process element 1 (freq=3)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Heap: []
Add 1 (freq=3)
Heap: [1]
โ
freq=3
Size: 1 โค k=2 โ Keep it
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Process element 2 (freq=2)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Heap: [1]
Add 2 (freq=2)
Heap: [2, 1] (min-heap, 2 has lower freq)
โ
freq=2 (min)
Size: 2 = k โ Heap full now!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Process element 3 (freq=1)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Heap: [2, 1]
min=2 (freq=2)
Compare: 3 (freq=1) vs min (freq=2)
1 < 2? YES โ 3 is LESS frequent than min in heap
Decision: SKIP 3!
WHY?
Heap has [2, 1] with freq [2, 3]
These are top 2 so far
Element 3 with freq=1 is less frequent
โ Not in top k
โ Don't add it
Heap: [2, 1] (unchanged)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Process element 4 (freq=4)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Heap: [2, 1]
min=2 (freq=2)
Compare: 4 (freq=4) vs min (freq=2)
4 > 2? YES โ 4 is MORE frequent than min in heap!
Decision: REPLACE min with 4!
WHY?
Current heap: [2, 1] with freq [2, 3]
Element 4 has freq=4
4 is more frequent than 2 (current min)
โ 4 should be in top k
โ Remove 2 (least of current top k)
โ Add 4
Step 1: Remove min
heap.poll() removes 2
Heap: [1]
Step 2: Add 4
heap.offer(4)
Heap: [1, 4] (min-heap adjusts)
Actually: [1, 4] where 1 has freq=3, 4 has freq=4
Min-heap by frequency: [1, 4]
โ
freq=3 (smaller)
Heap: [1, 4]
โ
min (freq=3)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESULT
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Final heap: [1, 4]
Elements with frequencies:
1 โ freq=3
4 โ freq=4
These are the top 2 most frequent! โ
Result: [1, 4] or [4, 1] (order doesn't matter)
Verification:
4 appears 4 times โ most frequent โ
1 appears 3 times โ 2nd most frequent โ
2 appears 2 times โ not in top 2
3 appears 1 time โ not in top 2
Implementation
import java.util.*;
/**
* Min-Heap of size k
* Time: O(n log k)
* Space: O(n)
*/
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// Step 1: Count frequencies
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Step 2: Min-heap by frequency (smallest freq on top)
PriorityQueue<Integer> minHeap = new PriorityQueue<>(
(a, b) -> freqMap.get(a) - freqMap.get(b)
);
// Step 3: Maintain heap of size k
for (int num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll(); // Remove least frequent
}
}
// Step 4: Extract result
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll();
}
return result;
}
}
โฐ Time: O(n log k) - Much better when k << n
๐พ Space: O(n) - HashMap + heap of size k
Why This is Better:
โ O(n log k) vs O(n log n) when k << n
โ Only maintains k elements in heap
โ Efficient eviction with min-heap
Example improvement:
n = 10,000, k = 10
Sorting: 10,000 ร log(10,000) โ 130,000 ops
Min-heap: 10,000 ร log(10) โ 33,000 ops
4x faster! โจ
But... follow-up wants O(n)!
Can we do even better?
๐ก The Second AHA Moment
PROBLEM ANALYSIS:
Min-heap is O(n log k), better than O(n log n).
But follow-up wants O(n)!
Can we eliminate the log factor?
OBSERVATION:
"Frequency values are BOUNDED!"
"Max frequency = n (all elements same)"
"Min frequency = 1"
"Frequency range: [1, n]"
What if we use this bounded range?
INSIGHT:
"Create BUCKETS by frequency!"
Bucket sort! โจ
Bucket[i] = list of elements with frequency i
bucket[0] = [] (no element has freq 0)
bucket[1] = [elements with freq 1]
bucket[2] = [elements with freq 2]
...
bucket[n] = [elements with freq n]
Then collect from high frequency to low!
TIME: O(n) to create buckets + O(n) to collect = O(n)! โ
This is BUCKET SORT - perfect when values are bounded! ๐ฏ
๐ฏ Approach 3: Bucket Sort (Optimal) โญโญโญ
The Optimal Solution
The Evolution of Thought:
Brute Force: Sort all
โฌ
Min-Heap: Keep top k
โฌ
Question: "Can we eliminate log factor?"
โฌ
Observation: "Frequency is bounded [1, n]!"
โฌ
Answer: "Use bucket sort by frequency!"
โฌ
Optimal: O(n) time - meets follow-up! โจ
Understanding Bucket Sort - Complete Explanation
What is Bucket Sort?
BUCKET SORT CONCEPT:
Normal sort: Compare elements โ O(n log n)
Bucket sort: Use value as index โ O(n)!
When to use:
โ Values are integers
โ Value range is bounded and known
โ Can create array of size (max - min + 1)
In our problem:
- Values: frequencies (how often element appears)
- Range: [1, n] where n = array length
- Can create: array of size n+1 (indices 0 to n)
Strategy:
1. Count frequencies (HashMap)
2. Create buckets by frequency
3. Collect from high to low frequency
WHY Bucket Sort Works Here - Critical Reasoning
The Core Question:
Why can we use bucket sort?
What makes frequency special?
Answer Through Reasoning:
REQUIREMENT FOR BUCKET SORT:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Bucket sort needs:
1. Integer values
2. Known bounded range
3. Can afford array of that size
For frequencies:
1. Frequencies are integers โ
(element appears 1, 2, 3... times)
2. Range is [1, n] โ
Proof:
Min frequency: 1 (element appears at least once)
Max frequency: n (all elements are same)
Example: [5,5,5,5] โ 5 appears 4 times (n=4)
3. Array of size n+1 is affordable โ
n โค 10^5 (from constraints)
Creating array of size 10^5 is fine!
So we CAN use bucket sort! โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HOW BUCKET SORT GIVES O(n):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 1: Count frequencies
Time: O(n) - scan array once
Step 2: Create buckets
For each unique element:
Add to bucket[frequency]
Time: O(unique elements) โค O(n)
Step 3: Collect top k
Scan buckets from high to low
Time: O(n) - at most n buckets
Stop when collected k elements
Total: O(n) + O(n) + O(n) = O(n) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WHY WE SCAN FROM HIGH TO LOW:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
We want TOP k most frequent.
Bucket[i] contains elements with frequency i.
High frequency = more frequent
Low frequency = less frequent
To get most frequent first:
Start from bucket[n] (highest possible frequency)
Go down to bucket[1] (lowest frequency)
Stop when collected k elements
Example:
n = 6
bucket[6] = []
bucket[5] = []
bucket[4] = [5] โ Start here (highest with elements)
bucket[3] = [1] โ Then here
bucket[2] = [2,6] โ Then here if needed
bucket[1] = [3,4]
If k=2: Take from bucket[4] and bucket[3] โ [5, 1] โ
Most frequent collected first!
Visual Tracking - Complete Example
Input: nums = [1,1,1,2,2,3], k = 2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 1: Count Frequencies
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Scan array:
1 appears 3 times
2 appears 2 times
3 appears 1 time
freqMap = {1: 3, 2: 2, 3: 1}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 2: Create Buckets by Frequency
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
n = 6 (array length)
Create buckets array of size n+1 (indices 0 to 6)
buckets = [[], [], [], [], [], [], []]
0 1 2 3 4 5 6
โ
freq (index)
For each element in freqMap:
Element 1 has freq=3 โ Add to bucket[3]
Element 2 has freq=2 โ Add to bucket[2]
Element 3 has freq=1 โ Add to bucket[1]
After placing:
buckets[0] = [] (no element has freq 0)
buckets[1] = [3] (element 3 appears 1 time)
buckets[2] = [2] (element 2 appears 2 times)
buckets[3] = [1] (element 1 appears 3 times)
buckets[4] = [] (no element has freq 4)
buckets[5] = [] (no element has freq 5)
buckets[6] = [] (no element has freq 6)
Visual:
freq 6: []
freq 5: []
freq 4: []
freq 3: [1] โ Highest frequency with elements
freq 2: [2]
freq 1: [3] โ Lowest frequency
freq 0: []
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 3: Collect Top K from High to Low Frequency
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
k = 2 (need 2 elements)
result = []
Scan from freq=6 down to freq=1:
Check freq=6: bucket[6] = []
Empty, continue
Check freq=5: bucket[5] = []
Empty, continue
Check freq=4: bucket[4] = []
Empty, continue
Check freq=3: bucket[3] = [1]
Has elements!
Add 1 to result
result = [1]
count = 1 (need 1 more)
Check freq=2: bucket[2] = [2]
Has elements!
Add 2 to result
result = [1, 2]
count = 2 (reached k!) โ STOP
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESULT
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
result = [1, 2]
Top 2 most frequent elements: [1, 2] โ
Verification:
1 appears 3 times โ most frequent โ
2 appears 2 times โ 2nd most frequent โ
3 appears 1 time โ not in top 2
Time complexity: O(n) โ
Meets follow-up requirement!
Another Example - Multiple Elements per Bucket
Input: nums = [4,1,-1,2,-1,2,3], k = 2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 1: Count Frequencies
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
freqMap = {4: 1, 1: 1, -1: 2, 2: 2, 3: 1}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 2: Create Buckets
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
n = 7
buckets[0] = []
buckets[1] = [4, 1, 3] โ Multiple elements with freq=1
buckets[2] = [-1, 2] โ Multiple elements with freq=2
buckets[3] = []
buckets[4] = []
buckets[5] = []
buckets[6] = []
buckets[7] = []
Note: Multiple elements can have SAME frequency!
That's why each bucket is a LIST!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 3: Collect Top K
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
k = 2
Scan from high to low:
Check freq=7 to freq=3: All empty
Check freq=2: bucket[2] = [-1, 2]
Has 2 elements!
Add both: result = [-1, 2]
count = 2 (reached k!) โ STOP
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESULT
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
result = [-1, 2]
Both appear 2 times (tied for most frequent) โ
Note: We could also return [2, -1] - order doesn't matter!
Implementation
import java.util.*;
/**
* Bucket Sort (Optimal)
* Time: O(n)
* Space: O(n)
*/
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// Step 1: Count frequencies
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Step 2: Create buckets by frequency
// buckets[i] = list of elements with frequency i
List<Integer>[] buckets = new List[nums.length + 1];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (int num : freqMap.keySet()) {
int freq = freqMap.get(num);
buckets[freq].add(num);
}
// Step 3: Collect top k from high frequency to low
int[] result = new int[k];
int index = 0;
for (int freq = buckets.length - 1; freq >= 0 && index < k; freq--) {
for (int num : buckets[freq]) {
result[index++] = num;
if (index == k) {
return result;
}
}
}
return result;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(Arrays.toString(sol.topKFrequent(new int[]{1,1,1,2,2,3}, 2))); // [1, 2]
System.out.println(Arrays.toString(sol.topKFrequent(new int[]{1}, 1))); // [1]
}
}
โฐ Time: O(n) - Meets follow-up requirement!
๐พ Space: O(n) - HashMap + buckets array
Why This is Optimal:
โ O(n) time - can't do better (must scan array)
โ Meets follow-up requirement
โ Simple and elegant
โ No sorting, no heap overhead
โ Perfect for bounded frequency values
Comparison:
Sorting: O(n log n)
Min-heap: O(n log k)
Bucket sort: O(n) โ BEST! โจ
๐ Approach Comparison - The Complete Growth Journey
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ Approach โ Time โ Space โ Key Insight โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโค
โ Sorting โ O(n log n) โ O(n) โ Sort by frequency โ
โ Min-Heap โ O(n log k) โ O(n) โ Keep top k only โ
โ Bucket Sort โ O(n) โ O(n) โ Frequency bounded โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ
THE COMPLETE LEARNING PROGRESSION:
Level 1: Brute Force (Sorting)
Thought: "Count frequencies โ Sort โ Take k"
Works? YES โ
Optimal? NO โ
Time: O(n log n)
Problem: Doesn't meet follow-up
Level 2: Optimization Insight #1
Question: "Do I need FULL sort for k elements?"
Realization: "Same as Kth Largest problem!"
Idea: "Use min-heap of size k!"
Level 3: Min-Heap Solution
Implementation: Min-heap tracks k most frequent
Result: O(n log k)
Better: Much better when k << n โ
Example: k=10, n=10000 โ 4x faster!
But: Still has log factor
Level 4: Optimization Insight #2
Question: "Can we eliminate log factor?"
Realization: "Frequency is bounded [1, n]!"
Idea: "Use bucket sort by frequency!"
Level 5: Bucket Sort (Optimal)
Implementation: Buckets by frequency
Result: O(n) time โ
Perfect: Meets follow-up requirement! โจ
Growth: Learned when to use bucket sort!
CONCRETE EXAMPLE (n=10,000, k=10):
Sorting:
Sort 10,000 elements
Time: 10,000 ร log(10,000) โ 130,000 ops
Min-Heap:
Process 10,000, maintain heap of 10
Time: 10,000 ร log(10) โ 33,000 ops
4x FASTER than sorting! ๐
Bucket Sort:
Count + create buckets + collect
Time: 10,000 + 10,000 + 10,000 = 30,000 ops
Slightly better than heap! โจ
For large k:
If k = 5,000:
Min-heap: 10,000 ร log(5,000) โ 120,000 ops
Bucket: Still 30,000 ops
Bucket MUCH better! ๐ฏ
๐ก Key Learnings - Your Complete Growth
WHAT YOU LEARNED:
1. PROBLEM PATTERN RECOGNITION:
โ "Top K" problems โ Think heap!
โ Similar to Problem 275 (Kth Largest)
โ Min-heap for "top k" items
2. WHY MIN-HEAP NOT MAX-HEAP:
โ Min-heap of size k: O(n log k)
โ Max-heap with all: O(n log n)
โ Size matters for complexity!
3. BUCKET SORT APPLICABILITY:
โ When values are bounded integers
โ Can create array of that size
โ Achieves O(n) without comparison!
4. SPACE-TIME TRADE-OFF:
โ All approaches use O(n) space
โ But time improves significantly
โ Bucket sort uses frequency bounds
5. FOLLOW-UP QUESTIONS:
โ "Better than O(n log n)" โ Think O(n)
โ Bounded values โ Bucket sort!
โ Know multiple approaches!
INTERVIEW STRATEGY:
Progressive Presentation:
"I can solve this in three ways:
Approach 1: Sort by frequency O(n log n)
Simple but doesn't meet follow-up.
Approach 2: Min-heap of size k O(n log k)
Better when k << n. Like Kth Largest problem.
Keep k most frequent, evict least frequent.
Approach 3: Bucket sort O(n)
Frequency bounded [1,n]. Create buckets.
Collect from high to low. Meets follow-up!
Let me implement bucket sort for O(n)."
Shows:
โ Multiple solution awareness
โ Complexity analysis
โ Pattern recognition
โ Optimal solution knowledge
This is REAL mastery! ๐ฑโ๐ณโ๐ฒโ๐
โ ๏ธ Common Mistakes
Mistake 1: Using max-heap with all elements
// โ WRONG - O(n log n), same as sorting!
PriorityQueue<Integer> maxHeap =
new PriorityQueue<>((a,b) -> freqMap.get(b) - freqMap.get(a));
for (int num : freqMap.keySet()) {
maxHeap.offer(num); // Add all
}
// โ CORRECT - Min-heap of size k: O(n log k)
PriorityQueue<Integer> minHeap =
new PriorityQueue<>((a,b) -> freqMap.get(a) - freqMap.get(b));
for (int num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll(); // Keep only k
}
}
Mistake 2: Wrong bucket size
// โ WRONG - Size should be n+1, not unique elements
List<Integer>[] buckets = new List[freqMap.size()];
// โ CORRECT - Bucket index is frequency (max = n)
List<Integer>[] buckets = new List[nums.length + 1];
Mistake 3: Wrong scan direction in bucket sort
// โ WRONG - Scanning from low to high frequency
for (int freq = 0; freq < buckets.length; freq++) {
// Gets least frequent first!
}
// โ CORRECT - Scan from high to low frequency
for (int freq = buckets.length - 1; freq >= 0; freq--) {
// Gets most frequent first!
}
Mistake 4: Not initializing bucket lists
// โ WRONG - NullPointerException!
List<Integer>[] buckets = new List[nums.length + 1];
buckets[freq].add(num); // NPE!
// โ CORRECT - Initialize each bucket
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
๐ฏ Pattern Recognition
Problem Type: Top K Selection by Frequency
Core Patterns: Heap + Bucket Sort
When to Apply:
โ "Top k" or "most/least frequent"
โ Need k elements by some criteria
โ Frequency/count based selection
โ Can use bounded value optimization
Recognition Keywords:
- "top k"
- "most frequent"
- "k most/least"
- "highest/lowest frequency"
Similar Problems:
- Kth Largest Element (LC 215) - Min-heap pattern
- Top K Frequent Words (LC 692) - Same pattern + trie
- Sort Characters by Frequency (LC 451) - Bucket sort
- K Closest Points (LC 973) - Min-heap for top k
Key Components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Frequency Map: Count occurrences โ
โ Min-Heap: Keep top k (when k << n) โ
โ Bucket Sort: When values bounded (O(n)) โ
โ Choose based on: k size and constraints โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Quick Revision Notes
๐ฏ Core Concept:
Find k most frequent elements. Brute force: Count โ sort by frequency O(n log n). Better: Min-heap of size k keeps k most frequent, evict least O(n log k). Optimal: Bucket sort - frequency bounded [1,n], create buckets, collect highโlow O(n). Follow-up satisfied!
โก Quick Implementations:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.PriorityQueue;
public class Solution {
public int[] topKFrequent(int[] a, int k) {
// return naive(a, k);
// return minHeap(a, k);
return bucketSort(a, k);
}
private int[] bucketSort(int[] a, int k) {
int[] res = new int[k];
// step 1: freq map
HashMap<Integer, Integer> freqMap = new HashMap<>();
for (int num : a) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// step 2: create a bucket array of size a.length + 1 where
// bucket[i] stores the elements having frequency i.
// why size a.length? if there is only 1 element that gets repeated
// in whole array, for example, 5 times, then bucket[5] = {1}
// Its an array whose element is a List.
// int[] => array of integers => new int[]
// List[] => array of lists => new ArrayList[]
// For example, for [1, 2, 1, 2, 1, 2, 3, 1, 3, 2] which has
// freqMap of {1:4, 2:4, 3:2}. bucket[0...10] will be created.
// bucket[0], bucket[1] bucket[3] => empty list
// bucket[2] => {3}
// bucket[4] => {1,4}
// bucket[5] to bucket[10] => again empty lists
int size = a.length;
List<Integer>[] bucket = new ArrayList[size + 1];
// initialize
for (int i = 0; i <= size; i++) {
bucket[i] = new ArrayList<>();
}
for (int key : freqMap.keySet()) {
bucket[freqMap.get(key)].add(key);
}
// step 3: loop through the bucket array from last to first as
// index indicates frequency and we need top k frequent elements
int count = 0;
for (int i = size; i >= 0; i--) {
for (int num : bucket[i]) {
if (count == k) {
return res;
}
res[count] = num;
count++;
}
}
return res;
}
private int[] minHeap(int[] a, int k) {
int[] res = new int[k];
// step 1: freq map
HashMap<Integer, Integer> freqMap = new HashMap<>();
for (int num : a) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// step 2: add to PQ instead of list for sorting based on freq later
// ArrayList<int[]> freqList = new ArrayList<>();
PriorityQueue<int[]> pq = new PriorityQueue<>((a1, a2) -> a2[1] - a1[1]);
for (int key : freqMap.keySet()) {
pq.offer(new int[] { key, freqMap.get(key) });
}
// step 3: put in res array
for (int i = 0; i < k; i++) {
res[i] = pq.poll()[0];
}
return res;
}
private int[] naive(int[] a, int k) {
int[] res = new int[k];
// step 1: freq map
HashMap<Integer, Integer> freqMap = new HashMap<>();
for (int num : a) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// step 2: convert to list for sorting based on freq later
ArrayList<int[]> freqList = new ArrayList<>();
for (int key : freqMap.keySet()) {
freqList.add(new int[] { key, freqMap.get(key) });
}
// step 3: sort the freq array based on freq in desc order
Collections.sort(freqList, (a1, a2) -> a2[1] - a1[1]);
// step 4: put in res array
for (int i = 0; i < k; i++) {
res[i] = freqList.get(i)[0];
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(Arrays.toString(s.topKFrequent(new int[] { 1, 1, 1, 2, 2, 3 }, 2))); // [1,2]
System.out.println(Arrays.toString(s.topKFrequent(new int[] { 1, 2, 1, 2, 1, 2, 3, 1, 3, 2 }, 2))); // [1,2]
}
}
๐ Key Insights:
- Natural Progression: Sort โ Min-heap โ Bucket sort
- Min-Heap Why: Keep ONLY k elements vs all elements
- Min Not Max: Size k gives O(n log k) not O(n log n)
- Bucket Sort When: Bounded integer values [1, n]
- Scan Direction: Highโlow frequency for most frequent first
- Multiple per Bucket: Same frequency = multiple elements in bucket
- Growth: O(n log n) โ O(n log k) โ O(n)! โ
๐ช Memory Aid:
"Count โ Min-heap of k โ Bucket by frequency!"
"Min-heap keeps k, not all!"
"Bounded values โ Bucket sort wins!" โจ
๐งช Edge Cases
Case 1: k = 1
Input: [1,1,1,2,2,3], k = 1
Output: [1]
Most frequent only
Case 2: k = unique elements
Input: [1,2,3], k = 3
Output: [1,2,3] (any order)
All elements have same frequency
Case 3: Single element
Input: [1], k = 1
Output: [1]
Case 4: Tied frequencies
Input: [1,1,2,2], k = 2
Output: [1,2]
Both have same freq, both included
All handled correctly! โ
๐ Complexity Analysis
Approach 1: Sorting
Time: O(n log n)
Count: O(n)
Sort: O(u log u) where u = unique โค n
Overall: O(n log n)
Space: O(n)
HashMap + list
Approach 2: Min-Heap
Time: O(n log k)
Count: O(n)
Heap ops: u elements ร O(log k) = O(n log k)
Extract: k ร O(log k) = O(k log k)
Overall: O(n log k)
Space: O(n)
HashMap + heap of size k
Approach 3: Bucket Sort
Time: O(n)
Count: O(n)
Create buckets: O(u) โค O(n)
Collect: O(n) worst case
Overall: O(n) โ
Space: O(n)
HashMap + buckets array
Optimal! Meets follow-up!
Happy practicing! ๐ฏ
Note: This problem is a MASTERCLASS in "Top K" optimization! You naturally start with sorting (easy but O(n log n)), realize min-heap is better for top k (O(n log k)), then discover bucket sort when values are bounded (O(n)). The progression teaches three critical patterns: (1) Min-heap for top k when k << n, (2) Why min-heap not max-heap (size matters!), (3) Bucket sort for bounded integer values. Understanding WHY we advance certain pointers, WHY min-heap over max-heap, and WHY bucket sort works here builds deep algorithmic intuition! True growth! ๐ชโจ๐