Topics › Basics › Learn Basic Hashing
Highest Occurring Element in an Array
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given a non-empty array of N integers, return the element that occurs the most times. If several elements share the highest frequency, return the smallest of them.
The problem is “count frequencies, then take a maximum”. The counting part is the basic hashing pattern; the new ingredient is the tie-break rule, which is easy to get wrong in every approach and is where most of the discussion below goes.
Constraints: 1 ≤ N ≤ 10^5, values fit in a 32-bit signed integer and may be negative.
Example 1
arr = [1, 2, 2, 3, 3, 3]
answer = 3 // 3 occurs three times, more than any other value
Example 2
arr = [1, 1, 2, 2]
answer = 1 // 1 and 2 both occur twice; the smaller one wins
Example 3
arr = [4, 4, -1, -1, -1]
answer = -1 // negative values are ordinary values
Approach 1: Brute force
Intuition. For each element, count how many times it appears by scanning the whole array, and keep the best candidate seen so far. A candidate replaces the current best if it has a strictly larger count, or the same count and a smaller value. Starting with bestCount = 0 guarantees the first element (count at least 1) becomes the initial best.
Algorithm.
- Set
best = arr[0],bestCount = 0. - For each index
i: count occurrences ofarr[i]in the whole array. - If
count > bestCount, orcount == bestCountandarr[i] < best, updatebestandbestCount. - Return
best.
static int bruteForce(int[] arr) {
int n = arr.length;
int best = arr[0], bestCount = 0;
for (int i = 0; i < n; i++) {
int count = 0;
for (int j = 0; j < n; j++) if (arr[j] == arr[i]) count++;
if (count > bestCount || (count == bestCount && arr[i] < best)) {
best = arr[i];
bestCount = count;
}
}
return best;
}
Complexity. Time O(n^2): every element triggers a full scan. Space O(1).
For n = 10^5 that is 10^10 comparisons, so we need the counting to be cheaper.
Approach 2: Better, sort and measure runs
Intuition. After sorting, equal values are adjacent, so the frequency of a value is the length of its run of consecutive equal entries. Walk the sorted array with two indices: i marks the start of a run and j advances until the value changes; j - i is the count. Because the runs are visited in increasing order of value, the first run that achieves the maximum count is also the smallest such value, so a strict > when updating the best automatically implements the tie-break. We sort a copy so the input is not reordered.
Algorithm.
- Copy and sort the array.
- Set
best = a[0],bestCount = 0,i = 0. - While
i < n: movejforward whilea[j] == a[i]; letcount = j - i. - If
count > bestCount, setbest = a[i],bestCount = count. Then seti = j. - Return
best.
static int better(int[] arr) {
int[] a = arr.clone();
Arrays.sort(a);
int best = a[0], bestCount = 0;
int i = 0;
while (i < a.length) {
int j = i;
while (j < a.length && a[j] == a[i]) j++; // a[i..j-1] is one run of equal values
int count = j - i;
if (count > bestCount) { // strict >, so an earlier (smaller) value keeps a tie
best = a[i];
bestCount = count;
}
i = j;
}
return best;
}
Complexity. Time O(n log n) for the sort; the run scan is O(n). Space O(n) for the sorted copy (O(1) if sorting in place is allowed).
This is also the right approach when you are not allowed extra hashing structures, or when you additionally need the answer for every frequency in sorted order.
Approach 3: Optimal, hash map
Intuition. Count every value in one pass with a HashMap<Integer, Integer>; then one pass over the map’s entries finds the entry with the largest count. The map’s iteration order is arbitrary, so unlike the sorted approach we cannot rely on seeing smaller values first, and the update rule must spell out the tie-break explicitly: replace the best on a strictly larger count, or on an equal count with a smaller value.
Algorithm.
- For each
vinarr,freq.merge(v, 1, Integer::sum). - Set
best = arr[0],bestCount = 0. - For each entry
(value, count): ifcount > bestCount, orcount == bestCountandvalue < best, update. - Return
best.
static int optimal(int[] arr) {
Map<Integer, Integer> freq = new HashMap<>();
for (int v : arr) freq.merge(v, 1, Integer::sum);
int best = arr[0], bestCount = 0;
for (Map.Entry<Integer, Integer> e : freq.entrySet()) {
int value = e.getKey(), count = e.getValue();
if (count > bestCount || (count == bestCount && value < best)) {
best = value;
bestCount = count;
}
}
return best;
}
Dry run on arr = [1, 2, 2, 3, 3, 3]. After counting, freq = {1: 1, 2: 2, 3: 3}. Suppose the map iterates in the order 2, 1, 3:
| entry | count > bestCount? | tie and smaller? | best, bestCount |
|---|---|---|---|
| start | 1, 0 | ||
| 2 → 2 | 2 > 0, yes | 2, 2 | |
| 1 → 1 | 1 > 2, no | 1 == 2, no | 2, 2 |
| 3 → 3 | 3 > 2, yes | 3, 3 |
Return 3. Any other iteration order gives the same result because the update rule is a total order on (count desc, value asc).
Complexity. Time O(n) on average: n map updates plus at most n entries to scan. Space O(d) for d distinct values.
If the values are known to be small non-negative integers (say at most 10^6), replace the map with an int[] counting array: same algorithm, no boxing, and the scan over the array in index order visits values smallest first, so the strict > tie-break of Approach 2 applies.
Edge cases and pitfalls
- Ties.
[1, 1, 2, 2]must return1. The brute force and hash map versions need the explicitcount == bestCount && value < bestclause; the sorted version gets it from the strict>because runs are visited in increasing order. Mixing the two (strict>with a hash map) returns an arbitrary tied value and is a real bug. - Single element.
[5]returns5: the first element always becomes the initial best becausebestCountstarts at0. - All distinct. Every value has count
1; the tie-break picks the smallest, so[3, 1, 2]returns1. - Negative values and extremes.
Integer.MIN_VALUEandInteger.MAX_VALUEare ordinary keys for a map and sort correctly; a counting array would need an offset and is impractical for the fullintrange. - Empty input. The statement guarantees
N ≥ 1; withN = 0the methods throw onarr[0]. If your version allows an empty array, decide on a sentinel and check for it first. - Mutating the input.
Arrays.sort(arr)reorders the caller’s array; sort a copy unless the statement allows modifying the input. - Not a majority problem. Boyer–Moore voting finds an element with count greater than
n / 2, which may not exist here. Use it only when that guarantee is given.
Related problems
- Counting Frequencies of Array Elements is the first half of this problem on its own, including an
O(1)extra-space variant. - Basic Hashing explains why the map lookups are
O(1)on average and when an array beats a map. - Majority Element-I is the special case where the winner is guaranteed to occur more than
n / 2times, which allows anO(1)space solution. - Sort Characters by Frequency ranks all keys by frequency instead of picking one.
- Two Sum is the other introductory hash map problem, looking up complements instead of counts.