1 · count→2 · bucket→3 · scan
nums phase 1 · pointer n walks the array
count hash map · value → times seen
freq bucket i holds the values seen exactly i times · bucket 0 stays empty
res collected from the highest bucket down · stop at k
public int[] topKFrequent(int[] nums, int k) { Map<Integer, Integer> count = new LinkedHashMap<>(); List<Integer>[] freq = new List[nums.length + 1]; for (int i = 0; i < freq.length; i++) { freq[i] = new ArrayList<>(); } for (int n : nums) { count.put(n, 1 + count.getOrDefault(n, 0)); } for (Map.Entry<Integer, Integer> e : count.entrySet()) { freq[e.getValue()].add(e.getKey()); } int[] res = new int[k]; int index = 0; for (int i = freq.length - 1; i > 0; i--) { for (int n : freq[i]) { res[index++] = n; if (index == k) { return res; } } } return res;}
- n
- —
- c
- —
- i
- —
- freq[i]
- —
- res
- —
Why it works
- The obvious way sorts: count, then sort the distinct values by count, O(n log n). A min-heap of size k gets O(n log k).
- A frequency is a small integer, between 1 and n. So use it as an array index: bucket
iholds the values seen exactlyitimes. No comparisons, no sort. - Scan from bucket n down to 1. Every value in a higher bucket is more frequent than every value in a lower one, so the first k values collected are the answer. Stop there: lower buckets are never touched.
- O(n) time: n counts, at most n values bucketed, n+1 buckets scanned. O(n) space for the map and the buckets.
- Pitfalls: size the array
n + 1, notn, because a value can appear n times ([1]needsfreq[1]). With ties at the boundary several answers are valid; LeetCode guarantees a unique one, so check by frequency, not by exact list. A plainHashMapiterates in no promised order, so this code usesLinkedHashMapto match the steps; any order inside a bucket is correct. - Follow-up: for a stream, or when k is tiny, keep a min-heap of size k: a
PriorityQueueordered bycount::getthat polls its smallest entry whenever it grows past k. Quickselect is O(n) on average.