Skip to content

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! ๐Ÿ’ชโœจ๐Ÿ†