Top K Frequent Elements
A medium problem from Arrays & Hashing. Try it first, open a hint when you are stuck, and read the solution when you are done.
- Medium
- Arrays & Hashing
The problem
Given a list of integers nums and a number k, return the k values that appear most often, in any order. The input is chosen so that the answer is unique: every value in the answer appears more often than every value left out.
What to write
function solve(nums: number[], k: number): number[]Example 1
Input nums = [1,1,1,2,2,3] k = 2 Output [1,2]
1 appears three times, 2 appears twice and 3 appears once, so the two most frequent values are 1 and 2.
Example 2
Input nums = [1] k = 1 Output [1]
The list holds a single value, so that value is the most frequent one.
Constraints
The set of k most frequent values is unique; 1 ≤ k ≤ distinct values.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Use the constraints
The answer depends only on how often each value appears, not on where it sits in the list. You are also promised that every value in the answer appears more often than every value left out, so there is exactly one right set of values.
Hint 2: Ask a smaller question
Take the list 1, 1, 1, 2, 2, 3 and pick its two most frequent values by hand. What did you have to work out about each value before you could choose?
Hint 3: Name the pattern
The work splits in two: first count how often each value appears, with a hash map from value to count, then choose the largest counts. The first half is frequency counting. Read the lesson on frequency counting.
Hint 4: State the invariant
After the counting pass, the map holds every different value exactly once, with its exact number of appearances. Nothing else about the list matters from then on.
Hint 5: Sketch the pseudocode
Count each value in a map. Take the map's keys and sort them by their counts, largest first. Return the first
kkeys.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Approach
Count occurrences, sort distinct values by frequency, and take the first k.
The slow way
For each different value, walk the whole list again to count it, then pick the largest counts. It is correct, but a list of n values that are all different costs n full passes, which is n × n steps.
The insight
One pass is enough to count everything if you keep a running count for each value in a hash map. After that the list itself is no longer needed: sorting the different values by their counts puts the answer in front.
Step by step
- Count how many times each value appears. A hash map keeps one running count per value, so the whole list is counted in a single pass.
- Order the different values from most to least frequent. Sorting by count puts the most frequent values at the front. Only the different values are sorted, not the whole list.
- The first k of them are the answer. After sorting, the most frequent values are the first ones. The problem promises that no value left out ties with one that is kept, so that set is unique.
The same numbered steps are the comments in each solution.
JavaScript
function solve(nums, k) {
// 1. Count how many times each value appears
const counts = new Map();
for (const n of nums) counts.set(n, (counts.get(n) || 0) + 1);
// 2. Order the different values from most to least frequent
const ordered = [...counts.keys()].sort(
(a, b) => counts.get(b) - counts.get(a),
);
// 3. The first k of them are the answer
return ordered.slice(0, k);
}TypeScript
function solve(nums: number[], k: number): number[] {
// 1. Count how many times each value appears
const counts = new Map<number, number>();
for (const n of nums) counts.set(n, (counts.get(n) || 0) + 1);
// 2. Order the different values from most to least frequent
const ordered = [...counts.keys()].sort(
(a, b) => counts.get(b)! - counts.get(a)!,
);
// 3. The first k of them are the answer
return ordered.slice(0, k);
}
Python
def solve(nums, k):
# 1. Count how many times each value appears
counts = {}
for num in nums:
counts[num] = counts.get(num, 0) + 1
# 2. Order the different values from most to least frequent
ordered = sorted(counts, key=counts.get, reverse=True)
# 3. The first k of them are the answer
return ordered[:k]
Example 1, traced
Input nums = [1,1,1,2,2,3] k = 2 Output [1,2]
| Step | Counts so far (value: times) |
|---|---|
| Read 1 | 1: 1 |
| Read 1 | 1: 2 |
| Read 1 | 1: 3 |
| Read 2 | 1: 3, 2: 1 |
| Read 2 | 1: 3, 2: 2 |
| Read 3 | 1: 3, 2: 2, 3: 1 |
| Order by count | Values in order: 1, 2, 3 |
| Keep the first 2 | Answer: 1, 2 |
Why it works
The map's counts are exact, because every element adds one to its own value's count. Sorting those counts from largest to smallest places the most frequent values first, and the problem promises that the top k form a unique set.
Time and space
Time O(n + u log u), extra space O(u). Counting reads each of the n values once. Sorting the u different values takes about u log u steps: O(n + u log u) time. The map and the sorted list hold one entry per different value, so the space is O(u).
n and m are input sizes, h is a tree's height, V and E are a graph's vertices and edges. The bounds describe this reference solution.
Watch out
- Sort the different values, not the original list. Sorting the list by frequency repeats each value as many times as it appears.
- Sort from the largest count to the smallest. The other direction returns the rarest values instead of the most frequent.
Pattern
Frequency counting. Builds on Valid Anagram.
Keep going
2 more problems solved with frequency counting
- Valid AnagramEasy
- Task SchedulerMedium