Counting with a hash map

Count how often each value occurs in one pass, then answer questions from the counts.

Pattern: Hashing20 min read
Quick recall3 questions
  1. 01How do you check that an array is sorted in one pass?

    Compare each value with the one right before it. One step down means it is not sorted.

  2. 02How do you build the reverse of a number without a string?

    Peel the last digit with n % 10, then rev = rev * 10 + digit. Drop the digit with integer division by 10.

  3. 03Which JavaScript structure stores a value for each key, with get, set and has?

    Map. Unlike a plain object, it keeps number keys as numbers and has a size.

"Which value shows up most?" sounds like a job for two loops: pick a value, then count it. That works on a small array and times out on a large one.

The fix is the move behind dozens of later problems: count everything once, in one pass, then answer from the counts.

Read the problem

Problem: Highest Occurring Element in an Arrayn ≤ 10⁵
it asks
Return the value that appears the most times. If several values tie, return the smallest of them.
example 1
in nums = [1, 2, 2, 3, 3, 3] out 3

3 appears three times.

example 2
in nums = [4, 4, 5, 5, 6] out 4

4 and 5 both appear twice. 4 is smaller.

limits
  • 1 ≤ n ≤ 10⁵
  • 1 ≤ nums[i] ≤ 10⁴
budget
About 10⁸ steps run in one second. With n up to 10⁵, aim for O(n log n) or faster.
Which pattern? Decide first, then open.

“appears the most times”count each value: a hash map of counts

“1 ≤ nums[i] ≤ 10⁴”the values are small, so an array of counts also works

pattern: Hashing

Ask these before you code:

  • A tie? The statement answers it: the smallest value wins. Plan the comparison for it.
  • All values different? Every count is 1, so it is a tie of all. Return the smallest value.
  • One item? Return it.
  • What range? Values from 1 to 10⁴. Keep that in mind: it opens a second way to count.

n reaches 10⁵. O(n²) is 10¹⁰ steps, so aim for O(n log n) or better.

Brute force: count each value from scratch

For each item, walk the whole array and count how often it appears. Keep the best so far.

count each value with a full scancomparisons 6
4
0
1
1
4
2
2
3
1
4
4
5
best{4:3}

1/7Take 4. Scan all 6 items: 4 appears 3 times. Best so far: 4.

Red cells are values counted again from scratch. 6 items cost 6 × 6 comparisons.
int mostFrequent(int[] nums) {
    int best = -1;
    int bestCount = 0;
    for (int x : nums) {
        int count = 0;
        for (int y : nums) if (y == x) count++; // count x from scratch
        if (count > bestCount || (count == bestCount && x < best)) {
            best = x;
            bestCount = count;
        }
    }
    return best;
}

The outer loop runs n times. For each, the inner loop reads all n items.

So n × n, O(n²). For n = 10⁵ that is 10¹⁰ comparisons: far too slow.

The key insight: keep a tally

The brute force counts 4 three times over. Each count throws away what the last one learned.

Think of counting votes on a whiteboard. You read each ballot once and add a mark next to its name.

At the end you read the totals. Nobody re-reads the pile for each name.

One pass fills the tally: one ▓ mark each time a value appears. The answer comes from the tally, not the array.

A is that whiteboard. Here the key is the value from the array, and what it stores is the count.

Optimal: one pass with a hash map

  1. Make an empty hash map of value to count.
  2. For each item, add 1 to its count. A value not seen yet starts from 0.
  3. Walk the map once. Keep the value with the highest count, and the smaller value on a tie.
one pass into a map of countssteps 1
4
0
1
1
4
2
2
3
1
4
4
5
count{4:1}

1/7Read 4. Not in the map yet, so its count becomes 1.

Same array, same answer: 9 steps instead of 36.
int mostFrequent(int[] nums) {
    Map<Integer, Integer> count = new HashMap<>();
    for (int x : nums) count.put(x, count.getOrDefault(x, 0) + 1);
    int best = -1;
    int bestCount = 0;
    for (Map.Entry<Integer, Integer> e : count.entrySet()) {
        int value = e.getKey();
        int c = e.getValue();
        if (c > bestCount || (c == bestCount && value < best)) {
            best = value;
            bestCount = c;
        }
    }
    return best;
}
  • A new value has no entry yet, so each language reads it as 0: getOrDefault(x, 0) in Java, count.get(x, 0) in Python, count.get(x) ?? 0 in TypeScript (where get returns undefined).
  • Shortcuts: count.merge(x, 1, Integer::sum) in Java. In Python, collections.Counter(nums) builds the whole map in one call.
  • c == bestCount && value < best: the tie rule from the statement, in one condition.
  • In Java, copy counts into int before you compare them, as above. Two Integer objects above 127 compared with == compare references, not values.
  • Time: n updates, then one walk over k distinct values. O(n + k), which is O(n).
  • Space: one entry per distinct value, O(k).

When the values are small: an array of counts

The limits say every value is between 1 and 10⁴. So an array can play the map: the value is the index.

count[v] holds how often v appears. Index 0 is never used here.
int[] count = new int[10001];
for (int x : nums) count[x]++;

Walk the indexes from small to large and keep the first highest count: ties go to the smaller value for free. 10,001 slots is tiny. For values up to 10⁹, or negative values, or strings, use a hash map.

Side by side

ApproachIdeaTimeExtra spaceFits n = 10⁵?
Count each from scratchscan all for each itemO(n²)O(1)no
Hash map of countsone pass, then read the tallyO(n)O(k)yes
Array of countsvalue is the indexO(n + range)O(range)yes, values ≤ 10⁴

Here k is the number of distinct values, and the range is the largest value allowed.

More answers from the same counts

Once the counts exist, the other problems on this lesson's list are a short walk over them:

  • Second Highest Occurring Element. Find the highest count, then the highest count below it. Return the smallest value with that second count. If every value has the same count, there is no second, so return -1. For [1, 2, 2, 3, 3, 3], the answer is 2.
  • Sum of Highest and Lowest Frequency. Add the largest and smallest counts. For [1, 2, 2, 3, 3, 3], that is 3 + 1 = 4.
int high = Collections.max(count.values());
int low = Collections.min(count.values());
return high + low;

Counting letters works the same way. The next lesson's anagram check counts the letters of two words and compares the tallies.

How to spot this pattern

Signal in the problemWhy it points here
"most frequent", "how many times"a count per value
"appears more than", "appears once"read the answer from the counts
"duplicates", "unique", "distinct"a count, or a Set
"same letters"two tallies, compared
values in a small rangean array of counts instead of a hash map

The one line to remember:

for (int x : nums) count.put(x, count.getOrDefault(x, 0) + 1);
take U forwardYouTube
Hashing, Maps, Time Complexity, Collisions: Strivers A2Z DSA Course
Remember4 points
  1. 01

    Count every value once, in one pass, then answer from the counts. That turns O(n²) re-counting into O(n).

  2. 02

    The line to remember: add 1 to the count of x, reading a missing count as 0. Java uses getOrDefault, Python get(x, 0), TypeScript ?? 0.

  3. 03

    When values sit in a small, non-negative range, an array indexed by value is a simpler tally.

  4. 04

    Read the tie rule in the statement and put it in the comparison: highest count, then smallest value.

Read more

Next: string basics, where palindromes, anagrams and rotations reuse two pointers and counting.

Practice

Solve these on the sheet after the lesson. Try each one for 20 minutes before you open its solution.

Striver's A2Z sheet0/4 solved
Open the sheet