Coding questions · Meta
Find top-K frequent values with tiebreaks
Given an integer array and k, return the k highest-frequency values, breaking ties by smaller numeric value then earlier first-occurrence index (lexicographically for strings), and returning all uniques when k is too large. The solution covers an O(n) bucket-counting algorithm plus O(n log k) heap and expected-linear quickselect alternatives with their trade-offs, derives floor((sqrt(8n+1)-1)/2) as the maximum number of distinct frequency values, and outlines input validation for robustness at scale.
Question
Given an integer array nums and an integer k, return the k values with the highest frequency. The array can contain up to 10^6 elements.
When multiple values share the same frequency, break ties deterministically:
- First by smaller numeric value (for string inputs, break ties lexicographically instead).
- Then by earlier first-occurrence index in the array.
Your solution should address all of the following:
- Return the correct
kvalues under the tie-breaking rules above. Ifkexceeds the number of distinct values, return all of the distinct values. - Provide an algorithm faster than
O(n log n)— for exampleO(n)orO(n log k)— and justify why it stays correct under the tie rules. - Discuss and compare the trade-offs among the main approaches: bucket counting by frequency, a size-
kheap, and a selection/quickselect-based partition. State the time and space complexity of each. - Follow-up: For an array of length
n, derive the maximum possible number of distinct frequency values achievable across its unique elements, and justify your formula. - Follow-up: How would you validate and guard against invalid inputs (e.g. non-integer elements, negative or non-integer
k, an extremely largen) while keeping the service robust?
Find top-K frequent values with tiebreaks
Given an integer array nums and an integer k, return the k values with the highest frequency.
When multiple values share the same frequency, break ties deterministically:
- First by smaller numeric value.
- Then by earlier first-occurrence index in the array.
Rules:
- Return the result as a list ordered from highest rank to lowest under the rules above (frequency descending, then smaller value, then earlier first occurrence).
- If
kis greater than or equal to the number of distinct values, return all of the distinct values (still in ranked order). - If
k <= 0, return an empty list.
Aim for an algorithm faster than O(n log n) overall (counting is O(n); only the relevant tie tiers need ordering).
Constraints
- 0 <= len(nums) <= 10^6
- Elements are integers (may be negative).
- k may be 0, negative, or larger than the number of distinct values.
- If k <= 0, return [].
- If k >= number of distinct values, return all distinct values in ranked order.
Examples
Example 1
Input: ([1, 1, 1, 2, 2, 3], 2)
Output: [1, 2]
Explanation: Frequencies: 1->3, 2->2, 3->1. Top 2 by frequency are 1 then 2.
Example 2
Input: ([4, 4, 5, 5, 6], 2)
Output: [4, 5]
Explanation: 4 and 5 both have frequency 2; tie broken by smaller value (4 before 5). 6 has frequency 1.
Hints
- Count each value's frequency in one O(n) pass, and record its first-occurrence index in the same pass.
- Define a single total order over the distinct values: frequency descending, then smaller numeric value, then earlier first-occurrence index. Sort or select by that key.
- Two distinct integers can never be equal, so the first-occurrence tiebreak only ever resolves ties between a value's own equal keys — it is a stable secondary key, harmless to keep.
- For an O(n)-style speedup over O(n log n), bucket values by frequency (buckets[f] = values seen f times) and walk buckets from high frequency to low, ordering only the tier you are currently emitting from.
- Handle k <= 0 (return []) and k >= distinct count (return all distinct, still ranked) as explicit branches.
Solution
Count frequencies in O(n), then select the top k by a fully-specified total order: frequency descending, then smaller value, then earlier first-occurrence index (lexicographic for strings). Bucket-counting by frequency gives O(n) time / O(n) space and is the headline approach; a size-k min-heap gives O(n log k) with O(k) space (best when k << n); quickselect gives expected-linear selection but is non-stable so needs an explicit total-order comparator. If k exceeds the number of distinct values, return all uniques. Follow-up 1: the max count of distinct frequencies is floor((sqrt(8n+1)-1)/2), from 1+2+...+m <= n. Follow-up 2: validate k (non-integer/negative/zero, clamp when too large) and elements at the boundary, stream input, and cap memory for huge n.
Approach
The task decomposes into three stages: (1) count frequencies, (2) select the top k by frequency, and (3) impose the deterministic tie-break order. Counting is unavoidably O(n). The interesting part is doing selection in better than O(n log n) while still honoring the tie rules.
Define a total order on candidate values that the whole solution sorts/selects by, so "top-k" is unambiguous:
Value
xranks higher than valueyiffreq[x] > freq[y], OR (freq[x] == freq[y]ANDx < y), OR (freq[x] == freq[y]ANDx == yis impossible since values are distinct keys). For strings, replacex < ywith lexicographic comparison.
The second tie-break (earlier first-occurrence index) only matters when two different values have both equal frequency AND equal numeric value, which cannot happen — distinct values are distinct numbers. So in the pure-integer case the first-occurrence rule is effectively redundant and is included to fully specify behavior; it becomes load-bearing only if values are compared by some coarser key (e.g. case-insensitive strings) where two distinct keys can compare equal. Handle it by storing each value's first-occurrence index during the counting pass and using it as the final comparator field.
Reference implementation (bucket counting, O(n))
from collections import defaultdict
def topKFrequent(nums, k):
if k <= 0:
return []
freq = defaultdict(int)
first_idx = {}
for i, x in enumerate(nums):
freq[x] += 1
if x not in first_idx:
first_idx[x] = i
if k >= len(freq):
# k exceeds the number of distinct values -> return all of them,
# still in the well-defined ranked order.
candidates = list(freq)
candidates.sort(key=lambda x: (-freq[x], x, first_idx[x]))
return candidates
# Bucket by frequency: bucket[f] holds all values seen exactly f times.
n = len(nums)
buckets = [[] for _ in range(n + 1)]
for x, f in freq.items():
buckets[f].append(x)
result = []
# Walk buckets from highest frequency to lowest.
for f in range(n, 0, -1):
if not buckets[f]:
continue
# Within a frequency tier, apply tie-breaks: smaller value, then
# earlier first-occurrence index.
buckets[f].sort(key=lambda x: (x, first_idx[x]))
for x in buckets[f]:
result.append(x)
if len(result) == k:
return result
return result
Why this is correct and meets the speed bound
- Counting and bucketing are
O(n). Frequencies are integers in[1, n], so bucket counting is an exact distribution sort over the frequency key with no comparisonlogfactor. - We collect values strictly in descending frequency order, so the first
kwe emit are exactly thekhighest-frequency values. - Within a single frequency tier we sort that tier by
(value, first_index)to enforce the tie rules. The total work of these per-tier sorts is bounded by sorting the distinct values that actually tie; in the worst case (all distinct values share one frequency) this degrades toO(d log d)overddistinct values, but in practice ties are small and the dominant term is theO(n)count. If a strictO(n)worst case is required even under pathological ties, replace the per-tier comparison sort with a radix/counting sort on the value key (when values are bounded integers).
Approach trade-offs
- Bucket counting (above):
O(n)time,O(n)space. Best asymptotics; needsO(n)buckets. Tie-breaks handled per tier. Ideal fornup to 10^6. - Size-
kmin-heap: push each(freq, tie-key)and keep the heap at sizek, evicting the smallest.O(d log k)time (d= distinct values),O(k)space. Wins whenkis tiny relative tonand memory is tight; comfortably beatsO(n log n)asO(n log k). The heap comparator must encode the full ranked order so evictions respect the tie rules. - Quickselect / partition: partition the
ddistinct values around the k-th best by the ranked key in expectedO(d)time, then sort just the chosenk(O(k log k)) if an ordered output is required. Expected linear, but worst-caseO(d^2)and not stable, so tie-breaks need an explicit total-order comparator (no equal keys) to stay deterministic.
Rule of thumb: bucket counting for the asymptotically-best O(n); the size-k heap when k << n and you want bounded memory; quickselect when you want expected-linear selection and can afford a non-stable partition.
Follow-up 1: maximum number of distinct frequency values
We want the largest set of distinct positive frequencies {f_1, f_2, ...} that can be realized by the unique elements of an array of length n, where the frequencies must sum to at most n (each element occurs f_i >= 1 times and total occurrences = n).
To maximize the count of distinct frequencies, use the smallest distinct positive integers: 1 + 2 + 3 + ... + m <= n. The largest such m satisfies m(m+1)/2 <= n, giving
max distinct frequencies = floor((sqrt(8n + 1) - 1) / 2).
Any leftover count n - m(m+1)/2 can be absorbed by bumping the largest frequency, which does not change the number of distinct frequencies. Example: for n = 10, 1+2+3+4 = 10, so up to 4 distinct frequency values; for n = 6, 1+2+3 = 6, up to 3.
Follow-up 2: input validation and robustness
Validate at the service boundary and fail fast with clear errors rather than corrupting the count:
k: reject non-integer or negativek; treatk == 0as an empty result; clampkto the number of distinct values (return all uniques) rather than erroring whenkis merely large.- Elements: if the contract is integers (or strings), reject or coerce non-conforming elements; never let a stray type silently change the hash/equality semantics of the frequency map.
- Scale: for extremely large
n, guard memory (theO(n)bucket array and the frequency map are bothO(n)); stream the input in one pass so you never hold two copies, and consider a configurable size cap / backpressure so a hostile input can't exhaust memory. - General: validate before allocation, return typed errors, and add a fast path for empty input. Property-test the comparator (antisymmetry/transitivity) so the tie order is provably total.