Elements Appearing More Than n/k Times generalizes the classic majority-element problem. Instead of one value that occupies more than half the array, we hunt for every value that occupies more than a 1/k share — and we do it with a clever voting trick that uses only a constant number of counters.
Problem. Given an integer array nums of length n and an integer k, return all values that
appear more than n/k times. (For k = 2 this is just the majority element.)
Example: nums = [3, 1, 2, 2, 1, 2, 3, 3], k = 3 → threshold is 8 / 3 ≈ 2.67, so we want values
appearing at least 3 times. Answer: [2, 3] (2 appears 3 times, 3 appears 3 times).
The slow way first
The obvious idea: count everything. Walk the array, tally each value in a hash map, then return every key whose tally is above n/k. That is O(n) time but O(n) extra space — the map can hold every distinct value.
Can we do better on space? The question to ask: how many values can possibly clear the bar? If a value appears more than n/k times, you can fit at most k − 1 such values before their counts would sum past n. So the answer never has more than k − 1 elements — and that cap is the whole reason a constant-space trick exists.
The idea: generalized Boyer-Moore voting
The classic majority trick keeps one (candidate, count) slot and cancels opposing votes. Generalize it to k − 1 slots. As we scan, for each value num we do exactly one of three things:
When a count hits 0 its slot frees up. After the pass, the k − 1 slots hold the only possible answers — but voting can leave false positives, so a second pass re-counts each survivor and keeps just the ones whose true frequency exceeds n/k.
Walk through it
Step through the animation with nums = [3, 1, 2, 2, 1, 2, 3, 3] and k = 3 (so 2 slots). The pointer i scans left to right and the counts slots fill, drain, and reset:
- 3 and 1 each grab a slot. Then 2 finds no free slot, so all counts drop — 3 and 1 both hit 0 and vanish.
- 2 and 1 grab the freed slots; the next 2 matches and bumps to count 2.
- A 3 with no free slot decrements all again — 1 falls out, 2 survives. The final 3 takes the open slot.
- Survivors are
{2, 3}. The second pass counts each in the full array: both appear 3 times, both clear the threshold of 2, so the answer is[2, 3].
Pseudocode
counts = empty map # at most k-1 (value -> running count) slots
for num in nums:
if num is already a candidate:
counts[num] += 1
else if fewer than k-1 candidates so far:
counts[num] = 1 # claim a free slot
else:
for every candidate c:
counts[c] -= 1 # cancel one vote each
if counts[c] == 0: drop c # free that slot
res = []
for c in counts: # second pass: verify
if real count of c in nums > n // k:
res.append(c)
return resThe Python solution
def majority_n_k(nums, k):
counts = {}
for num in nums:
if num in counts:
counts[num] += 1
elif len(counts) < k - 1:
counts[num] = 1
else:
for c in list(counts):
counts[c] -= 1
if counts[c] == 0:
del counts[c]
res = []
for c in counts:
if nums.count(c) > len(nums) // k:
res.append(c)
return rescountsholds at mostk − 1candidate slots — that bound is what keeps space constant.- If
numis already a candidate, we just bump its count (a confirming vote). - Otherwise, if there is room (
len(counts) < k - 1), we open a new slot for it. - Otherwise every slot is taken, so we decrement all counts; any candidate that reaches 0 is deleted, freeing its slot for later values.
- The second loop is the verification:
nums.count(c)re-counts the survivor across the whole array, and we keep it only if that true frequency beatslen(nums) // k.
Complexity
| Case | Time | Notes |
|---|---|---|
| Voting pass | O(n·k) (moderate) | decrement step touches up to k-1 slots |
| Verification pass | O(n·k) (moderate) | re-count each of up to k-1 survivors |
| Overall | O(n·k) (moderate) | linear in n for fixed k |
O(k) (moderate)The payoff is the O(k) space: we never hold more than k − 1 counters, versus O(n) for a plain frequency map. For the usual small k (often 3), that is effectively constant extra space.
When this pattern shows up
Whenever a problem asks for values that exceed a 1/k share of the array and hints at constant extra
space, reach for generalized Boyer-Moore voting. The key realization — at most k − 1 values can
qualify — both bounds the answer and sizes your counter set.
Voting alone is not proof. The surviving candidates are only possibilities; their leftover counts
do not equal real frequencies. You must run the second pass to verify each survivor actually exceeds
n/k, or you will return false positives on inputs where no value qualifies.
Practice
For nums = [3, 1, 2, 2, 1, 2, 3, 3] with k = 3, at i = 2 (value 2) both slots are full with 3 and 1. What happens to the counts?
1. Why can the answer contain at most k − 1 values?
2. What do we do when a value is not a candidate and all k − 1 slots are full?
3. Why is a second verification pass required?
4. What is the extra space used by this algorithm?