Chapter 7: Foundational Problem-Solving Patterns
7.01Beginner6 topics14 min1 interactive animation

Frequency and counting

Counting how often each value appears turns many “compare everything with everything” problems into a single pass. Use an array when values are small integers and a HashMap when they are not.

Learning goals

  • Count occurrences with a frequency array or a HashMap
  • Build a histogram and find the most frequent value
  • Count characters in a string with a 26-slot array
  • Find distinct and duplicated values in one pass

Prerequisites

ArraysHashMap basicsBig-O notation

Are two words anagrams? Comparing every letter with every other letter is slow. Counting the letters of each word and comparing the counts takes one pass.

7.01.1

Frequency arrays

When values are integers in a small range [0, k), allocate int[] count = new int[k] and do count[x]++ for each value. Every update is O(1), the whole pass is O(n + k), and no hashing is involved.

The animation counts the values, tracks how many are distinct, and shows how many values occur more than once, all in the same pass. Counters that turn red have more than one occurrence.

Worst

O(n + k)

Space

O(k)

Frequency array: count, distinct, duplicates

Interactive · edit the input

Your input

Try

int[] arr · length 11

3
1
4
1
5
9
2
6
5
3
5
012345678910
Being comparedAlready counted

count[]

00
01
02
03
04
05
06
07
08
09
Setup

One counter per possible value 0..9. Indexing by value makes each update O(1).

Step 1 / 24
Java
1public static int[] frequency(int[] arr, int maxValue) {
2 int[] count = new int[maxValue + 1];
3 int distinct = 0, duplicated = 0;
4 for (int x : arr) {
5 if (count[x] == 0) distinct++; // first time we see x
6 else if (count[x] == 1) duplicated++; // second time: x is a duplicate
7 count[x]++;
8 }
9 return count;
10}

Live state

0 reads

maxValue

9

distinct

0

duplicated

0

No condition evaluated on this step
Execution trace (dry run table)Show 24 steps
StepLineStateWhat happens
1L2maxValue=9 distinct=0 duplicated=0One counter per possible value 0..9. Indexing by value makes each update O(1).
2L5x=3 count[x]=0 distinct=1 duplicated=03 has not been seen before: a new distinct value.
3L7x=3 count[x]=1 distinct=1 duplicated=0count[3]++ → 1.
4L5x=1 count[x]=0 distinct=2 duplicated=01 has not been seen before: a new distinct value.
5L7x=1 count[x]=1 distinct=2 duplicated=0count[1]++ → 1.
6L5x=4 count[x]=0 distinct=3 duplicated=04 has not been seen before: a new distinct value.
7L7x=4 count[x]=1 distinct=3 duplicated=0count[4]++ → 1.
8L6x=1 count[x]=1 distinct=3 duplicated=11 was seen once already, so it is now a duplicated value.
9L7x=1 count[x]=2 distinct=3 duplicated=1count[1]++ → 2.
10L5x=5 count[x]=0 distinct=4 duplicated=15 has not been seen before: a new distinct value.
11L7x=5 count[x]=1 distinct=4 duplicated=1count[5]++ → 1.
12L5x=9 count[x]=0 distinct=5 duplicated=19 has not been seen before: a new distinct value.
13L7x=9 count[x]=1 distinct=5 duplicated=1count[9]++ → 1.
14L5x=2 count[x]=0 distinct=6 duplicated=12 has not been seen before: a new distinct value.
15L7x=2 count[x]=1 distinct=6 duplicated=1count[2]++ → 1.
16L5x=6 count[x]=0 distinct=7 duplicated=16 has not been seen before: a new distinct value.
17L7x=6 count[x]=1 distinct=7 duplicated=1count[6]++ → 1.
18L6x=5 count[x]=1 distinct=7 duplicated=25 was seen once already, so it is now a duplicated value.
19L7x=5 count[x]=2 distinct=7 duplicated=2count[5]++ → 2.
20L6x=3 count[x]=1 distinct=7 duplicated=33 was seen once already, so it is now a duplicated value.
21L7x=3 count[x]=2 distinct=7 duplicated=3count[3]++ → 2.
22L6x=5 count[x]=2 distinct=7 duplicated=35 was already counted as duplicated; only its count grows.
23L7x=5 count[x]=3 distinct=7 duplicated=3count[5]++ → 3.
24L9distinct=7 duplicated=3 most frequent=57 distinct values, 3 values appearing more than once, most frequent is 5 (3 times). One pass: O(n + k) time.
7.01.2

Frequency maps

For large, negative or non-integer keys (strings, objects), use a HashMap<K, Integer>. merge(key, 1, Integer::sum) increments in one call, and getOrDefault(key, 0) reads a count safely.

Expected O(1) per update, O(n) per pass. Iteration order of a HashMap is unspecified; use TreeMap if you need keys in sorted order, or LinkedHashMap to keep first-seen order.

Java
1Map<String, Integer> freq = new HashMap<>();
2for (String word : words) {
3 freq.merge(word, 1, Integer::sum);
4}
5int apples = freq.getOrDefault("apple", 0);
6
7// one-liner with streams
8Map<String, Long> counts = Arrays.stream(words)
9 .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

Average

O(n)

Space

O(distinct keys)

7.01.3

Histograms

A histogram is a frequency table drawn as bars. It answers questions such as “which value is most common?”, “how many values fall in each bucket?” and “what is the mode?”.

To bucket continuous data, map each value to a bucket index first, for example (score / 10) for scores 0-100, then count the bucket indices.

Java
1static int mode(int[] arr, int maxValue) {
2 int[] count = new int[maxValue + 1];
3 for (int x : arr) count[x]++;
4 int best = 0;
5 for (int v = 1; v <= maxValue; v++) {
6 if (count[v] > count[best]) best = v; // ties keep the smaller value
7 }
8 return best;
9}
7.01.4

Character counts

For lowercase English letters, int[26] indexed by c - 'a' is the fastest counter. Two strings are anagrams exactly when their 26 counts match: increment for one string, decrement for the other, and check that everything is zero.

For general Unicode text, use a HashMap<Integer, Integer> over code points (str.codePoints()), because a char cannot represent every character.

Java
1static boolean isAnagram(String s, String t) {
2 if (s.length() != t.length()) return false;
3 int[] count = new int[26];
4 for (int i = 0; i < s.length(); i++) {
5 count[s.charAt(i) - 'a']++;
6 count[t.charAt(i) - 'a']--;
7 }
8 for (int c : count) if (c != 0) return false;
9 return true;
10}

Worst

O(n)

Space

O(1) (26 counters)

7.01.5

Distinct elements

The number of distinct elements is the number of non-zero counters, or simply the size of a HashSet built from the values. Increment a distinct counter the first time a value's count goes from 0 to 1.

Arrays.stream(arr).distinct().count() does the same with a hash set internally. Sorting and counting value changes works too, in O(n log n) with O(1) extra space.

Java
1static int countDistinct(int[] arr) {
2 Set<Integer> seen = new HashSet<>();
3 for (int x : arr) seen.add(x);
4 return seen.size();
5}
7.01.6

Counting duplicates

A value is duplicated when its count reaches 2. Detecting that moment (the count goes from 1 to 2) counts each duplicated value once, no matter how many copies it has. Counting every extra copy instead gives n - distinct.

To return early on the first duplicate, Set.add returns false when the value is already present.

Java
1static boolean containsDuplicate(int[] arr) {
2 Set<Integer> seen = new HashSet<>();
3 for (int x : arr) {
4 if (!seen.add(x)) return true; // add() returns false if x was already there
5 }
6 return false;
7}

Practice ladder

Now try it yourself

Easy

  • Valid anagram
  • First unique character in a string
  • Contains duplicate

Medium

  • Top K frequent elements
  • Group anagrams
  • Sort characters by frequency

Advanced

  • Longest palindrome that can be built from given letters
  • Find all duplicates in an array of 1..n using O(1) extra space
  • Majority element in O(1) space (Boyer-Moore voting)

Key takeaways

  • A frequency array gives O(1) updates for small integer keys; a HashMap handles any key.
  • Character counts with int[26] solve anagram and permutation checks in O(n).
  • Distinct and duplicate counts fall out of the same counting pass.