Array · 27 of 36
Given an array of size n and a number k, find all elements that appear more than n/k times
Find every number that shows up more than n / k times, using a tiny bench of candidates instead of counting everything.
- Simple way
- time O(n)
room O(n) - Clever way
- time O(n)
room O(k)
The problem, simply
You get an array of n numbers and a number k. Find every number that appears more than n / k times.
A useful fact: at most k - 1 numbers can do this. If k numbers each appeared more than n / k times, together they would need more than n boxes.
A story to remember it
Picture it
n = 10, k = 3. A number needs at least 4 copies. 4 appears 4 times and wins. 3 appears 3 times, close but not enough.
- checking
- answer
Examples
A normal case
- In
arr = [3, 1, 2, 2, 1, 2, 3, 3], k = 4- Out
[2, 3]
n / k = 2. 2 and 3 appear 3 times each, 1 appears only 2 times.
With cancelling
- In
arr = [4, 1, 2, 4, 3, 4, 3, 5, 4, 3], k = 3- Out
[4]
n / k is about 3.33, so at least 4 copies are needed. This is the animated example.
Majority, k = 2
- In
arr = [2, 2, 1, 1, 2], k = 2- Out
[2]
More than half. This is the classic majority vote problem.
Nobody qualifies
- In
arr = [1, 2, 3, 4], k = 2- Out
[]
Every number appears once, and once is not more than 2.
Way 1: the simple way
Count every number with a hash map, then keep the ones with a big enough count. (An even simpler first try, counting each number by walking the whole array again, takes O(n^2).)
- Walk the array and count each number.
- Return every number whose count is more than
n / k.
class Solution {
public List<Integer> moreThanNByK(int[] arr, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int x : arr) count.merge(x, 1, Integer::sum);
List<Integer> out = new ArrayList<>();
for (Map.Entry<Integer, Integer> e : count.entrySet()) {
if (e.getValue() > arr.length / k) out.add(e.getKey());
}
return out;
}
}How long it takes: O(n). Extra room: one entry per different number, up to O(n). Fine for most uses, but if the array is a huge stream with millions of different numbers, the map gets huge.
Way 2: the clever way
Keep a bench of at most k - 1 candidates with tallies.
- For each
x: ifxis on the bench, add 1 to its tally. - Else if the bench has a free seat, put
xthere with tally 1. - Else lower every tally by 1 and remove the ones that reach 0. (
xis also thrown away: it cancels with them.) - After the walk, the bench holds every possible answer, plus maybe some false ones. Count the bench numbers for real in a second walk and keep those with more than
n / k.
A bench with k - 1 spots
01 / 13
n = 10 and k = 3, so we want numbers that appear more than 10 / 3 times, which for whole counts means at least 4 times. At most 2 numbers can do that, so the bench has 2 spots.
- more than
- 3
- looking now
- checking
- answer
- rejected
- just added
- ignored
class Solution {
public List<Integer> moreThanNByK(int[] arr, int k) {
List<Integer> out = new ArrayList<>();
if (k < 2) return out; // nothing appears more than n times
Map<Integer, Integer> bench = new HashMap<>();
for (int x : arr) {
if (bench.containsKey(x)) {
bench.put(x, bench.get(x) + 1); // team already seated
} else if (bench.size() < k - 1) {
bench.put(x, 1); // a free seat
} else { // full: k different numbers cancel
Iterator<Map.Entry<Integer, Integer>> it = bench.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> e = it.next();
if (e.getValue() == 1) it.remove();
else e.setValue(e.getValue() - 1);
}
}
}
for (int key : bench.keySet()) bench.put(key, 0); // second walk: real counts
for (int x : arr) if (bench.containsKey(x)) bench.put(x, bench.get(x) + 1);
for (Map.Entry<Integer, Integer> e : bench.entrySet()) {
if (e.getValue() > arr.length / k) out.add(e.getKey());
}
return out;
}
}How long it takes: a cancel costs O(k), which looks like O(n * k) in total. But each cancel removes k - 1 tally points, and tallies only ever gain n points, so there are at most n / (k - 1) cancels. The total is O(n). Extra room: the bench, O(k).
Watch out for
- "More than
n / k" is strict. Withn = 8,k = 4, a number appearing exactly 2 times does not count. With integer division,count > n / kis still exactly right. - When the bench is full and
xis new,xis not seated. It cancels with the others. - Removing from a
HashMapwhile looping over it needs an iterator'sremove, or you get aConcurrentModificationException.
What interviewers ask next
k = 2, the majority? Keep one candidate and one tally: same number adds 1, different number subtracts 1, tally 0 means take the new number. That is Boyer-Moore voting,O(n)time andO(1)room.k = 3(LeetCode Majority Element II)? Two candidates, two tallies, then verify. Same as this with a bench of 2.- Why at most
k - 1answers? Ifknumbers each had more thann / kcopies, there would be more thannboxes. - A huge stream where you cannot do a second walk? The bench still gives every heavy number, plus false ones, and each tally is at most
n / kbelow the true count. This is the Misra-Gries summary used for heavy hitters in network traffic.
Remember this
Grown-up words: the bench is a set of candidates with counters, cancelling k different items is the Misra-Gries heavy hitters algorithm, and k = 2 is the Boyer-Moore majority vote.