The k most frequent values
Given an array nums and an integer k, return the
k values that occur most often. Order them by decreasing frequency; when two values
occur equally often, the smaller value comes first.
k is at most the number of distinct values.
Examples
| Input | Output | Why |
|---|---|---|
nums = [1, 1, 1, 2, 2, 3], k = 2 | [1, 2] | 1 occurs three times, 2 twice. |
nums = [4, 4, 4, 6, 6, 6, 5, 5], k = 2 | [4, 6] | 4 and 6 tie on three; the smaller goes first. |
nums = [9, 8, 7], k = 3 | [7, 8, 9] | Everything ties, so the order is by value. |
Constraints
- 1 ≤ n ≤ 10⁵
- −10⁴ ≤ nums[i] ≤ 10⁴
- 1 ≤ k ≤ number of distinct values
Hints
Hint 1
Two separate jobs are hiding in one sentence: counting how often each value appears, and ordering the values by that count.
Hint 2
Counting is one pass with a hash map from value to count. You never need the original order again.
Hint 3
Sort the distinct values by (count descending, value ascending) and take the first k. Then ask whether sorting is the best you can do.
Dry run on [4,4,4,6,6,6,5,5],2
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 11nums4041426364655657k2
First job: count. One pass, a map from value to how often it has appeared.
-
Step 2 / 11nums40i41426364655657counts{4:1}
4 → 1.
-
Step 3 / 11nums4041i426364655657counts{4:2}
4 → 2.
-
Step 4 / 11nums404142i6364655657counts{4:3}
4 → 3.
-
Step 5 / 11nums40414263i64655657counts{4:3, 6:1}
6 → 1.
-
Step 6 / 11nums4041426364i655657counts{4:3, 6:2}
6 → 2.
-
Step 7 / 11nums404142636465i5657counts{4:3, 6:3}
6 → 3.
-
Step 8 / 11nums40414263646556i57counts{4:3, 6:3, 5:1}
5 → 1.
-
Step 9 / 11nums4041426364655657icounts{4:3, 6:3, 5:2}
5 → 2.
-
Step 10 / 11by count406152counts4:3 6:3 5:2
Second job: order the 3 distinct values by count, largest first, smaller value first on a tie: [4, 6, 5].
-
Step 11 / 11by count406152k2
Take the first 2: [4, 6].
How to think about it
Split the question in two. Counting is a hash map from value to frequency, filled in
one pass — the whole array reduces to at most d (value, count) pairs, where
d is the number of distinct values.
Ordering is then a sort of those d pairs by count, largest first, breaking
ties by value. Take the first k. That costs O(n + d log d), and since
d ≤ n it is never worse than sorting the input.
The tie rule is not decoration. "The k most frequent" is ambiguous whenever counts tie, and in an interview the right move is to ask how ties break before writing anything — an answer that depends on hash-map iteration order is not an answer.
If ties did not matter there is an O(n) route: counts lie between 1 and n, so put each value in a bucket indexed by its count and read the buckets from the top. With the tie rule you still sort inside each bucket, which is why the plain sort is the version worth writing first.
Complexity. Time O(n + d log d) · Space O(d)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import Counter
def top_k_frequent(nums, k):
count = Counter(nums) # value -> frequency
order = sorted(count, key=lambda x: (-count[x], x)) # most frequent first, then smaller
return order[:k]#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
vector<int> solve(vector<int> nums, int k) {
unordered_map<int, int> count; // value -> frequency
for (int x : nums) count[x]++;
vector<pair<int, int>> v(count.begin(), count.end());
sort(v.begin(), v.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
return a.second != b.second ? a.second > b.second : a.first < b.first;
});
vector<int> out;
for (int i = 0; i < k; i++) out.push_back(v[i].first);
return out;
}import java.util.*;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>(); // value -> frequency
for (int x : nums) count.merge(x, 1, Integer::sum);
List<Integer> vals = new ArrayList<>(count.keySet());
vals.sort((a, b) -> {
int ca = count.get(a), cb = count.get(b);
return ca != cb ? cb - ca : Integer.compare(a, b);
});
int[] out = new int[k];
for (int i = 0; i < k; i++) out[i] = vals.get(i);
return out;
}
}function topKFrequent(nums, k) {
const count = new Map(); // value -> frequency
for (const x of nums) count.set(x, (count.get(x) || 0) + 1);
return [...count.keys()]
.sort((a, b) => count.get(b) - count.get(a) || a - b) // most frequent first, then smaller
.slice(0, k);
}use std::collections::HashMap;
fn solve(nums: &[i32], k: usize) -> Vec<i32> {
let mut count: HashMap<i32, usize> = HashMap::new(); // value -> frequency
for &x in nums {
*count.entry(x).or_insert(0) += 1;
}
let mut v: Vec<(i32, usize)> = count.into_iter().collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0))); // most frequent first, then smaller
v.into_iter().take(k).map(|(x, _)| x).collect()
}import qualified Data.Map.Strict as M
import Data.List (sortBy)
import Data.Ord (comparing, Down (..))
solve :: [Int] -> Int -> [Int]
solve xs k = take k (map fst (sortBy (comparing (\(x, c) -> (Down c, x))) (M.toList counts)))
where
counts = M.fromListWith (+) [(x, 1 :: Int) | x <- xs] -- value -> frequency
Where people lose marks
- Sorting the original array and scanning runs. It works, but it sorts n values when only d distinct ones matter.
- Relying on the iteration order of a hash map to break ties. It varies between languages, and between runs in some.
- Returning (value, count) pairs, or counts, when the question asks for values.
- Reaching for a heap of size k and forgetting that the tie-break has to live in the heap's comparison too.
Variants to try
- Ties do not matter. (Bucket by count: O(n).)
- k is tiny and n is huge. (A min-heap of size k: O(n log k), and O(k) extra beyond the counts.)
- The values arrive as a stream too large to store. (Exact answers are impossible in bounded memory; Count-Min sketch or Misra–Gries give approximate heavy hitters.)
Next problems
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Medium20 minCount subarrays with a given sum
Negative numbers break the sliding window. Prefix sums in a hash map fix it, and the counting detail is where people slip.
Open the problem →Write a solution and run it against the real test table.
Keep going
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Medium20 minCount subarrays with a given sum
Negative numbers break the sliding window. Prefix sums in a hash map fix it, and the counting detail is where people slip.
Open the problem → Arrays and hashingThe pattern behind it
Trade memory for time: a hash map answers "have I seen this?" in O(1), and most array problems reduce to asking it the right question.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.