{x}
blog image

Top K Frequent Elements

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 <= 105
  • -104 <= nums[i] <= 104
  • 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.

Solution Explanation: Top K Frequent Elements

This problem asks to find the top k most frequent elements in an array. The optimal solution avoids O(n log n) sorting by using a hash table to count frequencies and a min-heap (priority queue) to efficiently track the top k frequent elements.

Approach:

  1. Frequency Counting: We use a hash map (dictionary in Python) to count the occurrences of each element in the input array nums. This step takes O(n) time, where n is the length of nums.

  2. Min-Heap: We employ a min-heap data structure. A min-heap is a binary tree where the smallest element is always at the root. We use a min-heap because we only need to keep track of the top k frequent elements. As we iterate through the frequencies, if we encounter an element with a frequency greater than the smallest element in the heap (the root), we remove the smallest element and insert the new element. This ensures that the heap always contains the k most frequent elements seen so far.

  3. Result: After processing all elements, the min-heap will contain the top k frequent elements. We extract these elements from the heap and return them as the result.

Time and Space Complexity:

  • Time Complexity: O(n log k). The frequency counting takes O(n) time. The heap operations (insertion and deletion) take O(log k) time each, and we perform these at most n times. Therefore, the dominant factor is O(n log k). This is significantly better than O(n log n) for large n and small k.

  • Space Complexity: O(n + k). The hash map can, in the worst case, store all unique elements from the array (O(n)). The min-heap stores at most k elements (O(k)).

Code Examples:

The code implementations below demonstrate the solution in various programming languages. They all follow the same basic approach outlined above. Note that the Python solution leverages the Counter object and most_common() method for a more concise implementation. This internal implementation likely uses a similar heap-based approach for efficiency.

Python:

from collections import Counter
import heapq
 
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        count = Counter(nums)  #Efficient frequency counting
        return heapq.nlargest(k, count.keys(), key=count.get) #Efficient top k selection

Java:

import java.util.*;
 
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> count = new HashMap<>();
        for (int num : nums) {
            count.put(num, count.getOrDefault(num, 0) + 1);
        }
 
        PriorityQueue<Map.Entry<Integer, Integer>> minHeap = 
            new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue));
 
        for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
            minHeap.offer(entry);
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }
 
        int[] result = new int[k];
        for (int i = k - 1; i >= 0; i--) {
            result[i] = minHeap.poll().getKey();
        }
        return result;
    }
}

C++:

#include <iostream>
#include <vector>
#include <map>
#include <queue>
 
using namespace std;
 
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> count;
        for (int num : nums) {
            count[num]++;
        }
 
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
 
        for (auto const& [key, val] : count) {
            minHeap.push({val, key});
            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }
 
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = minHeap.top().second;
            minHeap.pop();
        }
        return result;
    }
};

The other languages (Go, TypeScript, Rust) would follow a similar structure, utilizing their respective built-in or library-provided hash maps and priority queues. The core algorithm remains consistent across all languages.