LeetCode

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.

mediumtime: O(n)space: O(n)lang: Java
arrayhash-table

Problem

Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.

Complexity

  • Time: O(n)
  • Space: O(n)

Solution (java)

import java.util.*;
 
public class TopKFrequentElements {
    public int[] topKFrequent(int[] nums, int k) {
        validateInputs(nums, k);
        Map<Integer, Integer> freqByValue = buildFrequencyMap(nums);
        List<Integer>[] buckets = bucketizeByFrequency(freqByValue, nums.length);
        int[] topK = selectTopKFromBuckets(buckets, k);
        // Optional: validateEdgeCasesAndInvariants(nums, k, topK);
        return formatOutput(topK);
    }
 
    /**
     * Responsibility: Validate basic preconditions for nums and k.
     * Inputs: nums (int[]), k (int)
     * Outputs: none
     * Side Effects: throws IllegalArgumentException on invalid inputs
     * Invariants: if returns, nums != null, nums.length >= 1, 1 <= k <= nums.length
     * Time: O(1)
     * Space: O(1)
     */
    private static void validateInputs(int[] nums, int k) {
        if(nums.length == 0 || k < 1 || k > nums.length) {
            throw new IllegalArgumentException("Invalid input: nums must be non-empty and 1 <= k <= nums.length");
        }
    }
 
    /**
     * Responsibility: Build a frequency map value -> count.
     * Inputs: nums (int[])
     * Outputs: Map<Integer,Integer> where counts >= 1
     * Side Effects: allocations only
     * Invariants: sum(counts) == nums.length
     * Time: O(n)
     * Space: O(u)
     */
    private static Map<Integer, Integer> buildFrequencyMap(int[] nums) {
        Map<Integer, Integer> freqByValue = new HashMap<>();
        for(int num : nums) {
            freqByValue.put(num, freqByValue.getOrDefault(num, 0) + 1);
        }
        return freqByValue;
    }
 
 
    /**
     * explanation: the above frequency map for input [3,3,5,7,7,9,9,9] would be:
     * freqByValue = {
     *     3 -> 2,    // value 3 appears 2 times
     *     5 -> 1,    // value 5 appears 1 time
     *     7 -> 2,    // value 7 appears 2 times
     *     9 -> 3     // value 9 appears 3 times
     * }
     * n = 4  // array length (so max possible frequency is 4)
     *
     * so bucketizeByFrequency would produce:
     * - A list-of-lists where the index is the frequency, and the value at that index is all number with that frequency.
     * buckets[0] = []           // no values appear 0 times (unused)
     * buckets[1] = [5]          // value 5 appears 1 time
     * buckets[2] = [3, 7]       // values 3 and 7 appear 2 times
     * buckets[3] = [9]          // value 9 appears 3 times
     * buckets[4] = []           // no values appear 4 times (unused)
     *
     *
     * value=3, freq=2 → buckets[2].add(3) → buckets[2] now has [3]
     * value=5, freq=1 → buckets[1].add(5) → buckets[1] now has [5]
     * value=7, freq=2 → buckets[2].add(7) → buckets[2] now has [3, 7]
     * value=9, freq=3 → buckets[3].add(9) → buckets[3] now has [9]
     */
 
    /**
     * Responsibility: Create frequency buckets indexed by count.
     * Inputs: freqByValue (Map), n (array length)
     * Outputs: List<Integer>[] buckets of size n+1; buckets[f] contains values with frequency f
     * Side Effects: allocations only
     * Invariants: each key appears in exactly one bucket at its frequency; indices in [1..n]
     * Time: O(u)
     * Space: O(n + u)
     */
    private static List<Integer>[] bucketizeByFrequency(Map<Integer, Integer> freqByValue, int n) {
        List<Integer>[] buckets = new List[n + 1]; // Array of n+1 lists
        for(int i = 0; i <= n; i++) {
            buckets[i] = new ArrayList<>(); // Initialize each to empty
        }
 
        for(Map.Entry<Integer, Integer> entry : freqByValue.entrySet()) {
            int value = entry.getKey();  // e.g., 3
            int frequency = entry.getValue(); // e.g., 2
            buckets[frequency].add(value); // Put 3 into buckets[2]
        }
        return buckets;
    }
 
    /**
     * Responsibility: Select exactly k values by scanning buckets from high freq to low.
     * Inputs: buckets (List<Integer>[]), k (int)
     * Outputs: int[] of length k
     * Side Effects: none
     * Invariants: output length == k; all elements distinct
     * Time: O(n + k) worst-case
     * Space: O(k)
     */
    private static int[] selectTopKFromBuckets(List<Integer>[] buckets, int k) {
        int[] topK  = new int[k];
        int index = 0;
        for(int freq = buckets.length -1; freq >= 1 && index < k; freq--) {
            for(int value : buckets[freq]) {
                topK[index] = value;
                index++;
                if(index == k) {
                    break;
                }
            }
        }
        return topK;
    }
 
    /**
     * Responsibility: Return the result in required shape.
     * Inputs: topK (int[])
     * Outputs: int[] result
     * Side Effects: none
     * Invariants: result.length == topK.length
     * Time: O(1)
     * Space: O(1)
     */
    private static int[] formatOutput(int[] topK) {
        return topK;
    }
 
    /**
     * Responsibility: Debug/test-only invariant validation for edge cases.
     * Inputs: nums, k, result
     * Outputs: none
     * Side Effects: may throw/assert on failure
     * Invariants: result length k; values unique; each value appears in nums
     * Time: O(n) (debug/tests)
     * Space: O(k) or O(u) (debug/tests)
     */
    private static void validateEdgeCasesAndInvariants(int[] nums, int k, int[] result) {
        assert result.length == k : "Result length must be k";
        Set<Integer> seen = new HashSet<>();
        Set<Integer> numsSet = new HashSet<>();
        for(int num : nums) {
            numsSet.add(num);
        }
        for(int value : result) {
            assert !seen.contains(value) : "Values in result must be unique";
            assert numsSet.contains(value) : "Values in result must appear in input";
            seen.add(value);
        }
    }
 
    public static void main(String[] args) {
        TopKFrequentElements solution = new TopKFrequentElements();
        int[] nums = {1,1,1,2,2,3};
        int k = 2;
        int[] topK = solution.topKFrequent(nums, k);
        System.out.println(Arrays.toString(topK)); // Expected: [1, 2]
    }
}