Hashing & Counting

Easyhash map · hash set · counting · arrays · strings⏱ 6 min readUpdated

Use hash maps, hash sets and fixed-size count arrays to replace repeated scanning with constant-time lookups, frequency tables and canonical grouping keys.

PatternHashing & Counting

Instinct Need "have I seen this before?", "how many times?" or "group things that look alike"? Trade memory for O(1) lookups with a HashMap, HashSet or int[26].

Memory hack Don't search, remember - every element checks in at the front desk, and the desk answers instantly.

At a glance

When to use it

  • A brute force has an inner loop whose only job is to find a partner or a repeat
  • You need "seen before?", "how many times?" or "which index?" answers
  • Items must be grouped by a shared signature such as sorted letters
  • The input is unsorted and sorting would lose the original indices

Key idea

Remember instead of search - record each element in a HashMap, HashSet or int[26] and query it in O(1), trading O(n) memory to turn an O(n^2) scan into a single O(n) pass.

Typical complexity

O(n) time, O(n) space (O(1) for a fixed alphabet)

How to spot it If the problem says "two numbers add up to", "duplicate", "anagram" or "group together" and asks for a fast answer on unsorted data, think hashing first.

The instinct

Whenever a brute-force solution contains an inner loop whose only job is to find something (a complement, a duplicate, a previous position, a matching group), that inner loop can usually be replaced by a hash lookup. Hashing is the classic space-for-time trade: you spend O(n) memory so every "is it there?" or "how many?" question costs O(1) on average.

The pattern has three faces: membership (HashSet: seen it or not), frequency (HashMap or int[26]: how many), and grouping (HashMap from a canonical key to a list: which items belong together).

Recognise it

Signal in the problem Why it points here
"Find two numbers that add up to target" in an unsorted array Store each value's index; look up target - x instead of scanning.
"Contains duplicate", "first repeated", "within distance k" A set (or map of last index) answers "seen before?" in O(1).
"Anagram", "same letters", "ransom note", "can be formed from" Compare frequency tables, often an int[26].
"Group strings that are equivalent" Map a canonical signature (sorted letters, count vector) to a bucket.
"Isomorphic", "follows the same pattern" Two maps enforce a one-to-one character mapping both ways.
"Longest consecutive sequence" in O(n) without sorting Put all values in a set; start counting only at sequence starts.
"Count pairs with a property" Count values as you go; each new element pairs with the matching count so far.

Not this pattern if...

  • The input is sorted and you need a pair: two pointers give O(1) space.
  • The question is about a contiguous window with a constraint: use Sliding Window (often with a map inside it).
  • You need subarray sums: use Prefix Sum (whose fast version stores prefix values in a map).
  • Order or nearest-greater queries matter: a map loses ordering; consider a TreeMap, heap or monotonic stack.

Memory hack

Tip

Don't search, remember. Every element checks in at the front desk; later, anyone can ask the desk "is X staying here?" and get an instant answer instead of knocking on every door. Analogy: a hotel concierge with a guest ledger. Walking the corridors to find a guest is O(n); asking the concierge is O(1).

Say it out loud in the interview:

  1. "The brute force has an inner loop that only searches, so I'll replace it with a hash lookup."
  2. "What exactly is my key and what is my value: index, count, or list of items?"
  3. "Do I check before I insert, or insert before I check?" (Decides whether an element can pair with itself.)

How it works

Walk the input once. For each element, first query the structure for whatever partner or history you need, then record the current element. The order of these two steps is what prevents an element from matching itself.

Trace for Two Sum on [2, 7, 11, 15], target 9:

i x need = 9 - x map before found? map after
0 2 7 {} no {2:0}
1 7 2 {2:0} yes, index 0 return [0, 1]

For grouping (Group Anagrams), the trick is a canonical key: two strings belong together exactly when their keys are equal. Sorting the letters gives a key in O(k log k); a 26-slot count vector turned into a string gives one in O(k).

For Longest Consecutive Sequence, load everything into a set, then only start counting from values v where v - 1 is absent. Each value is visited at most twice overall, so the whole scan is O(n).

Java template

java
import java.util.*;

class HashingToolkit {
    // 1) Complement lookup: query first, then record.
    static int[] pairWithTarget(int[] nums, int target) {
        Map<Integer, Integer> seenAt = new HashMap<>();   // value -> index
        for (int i = 0; i < nums.length; i++) {
            Integer j = seenAt.get(target - nums[i]);      // problem-specific partner
            if (j != null) return new int[]{j, i};
            seenAt.put(nums[i], i);
        }
        return new int[]{-1, -1};
    }

    // 2) Frequency table: int[26] for lowercase letters, HashMap otherwise.
    static boolean sameLetters(String a, String b) {
        if (a.length() != b.length()) return false;
        int[] cnt = new int[26];
        for (int i = 0; i < a.length(); i++) {
            cnt[a.charAt(i) - 'a']++;
            cnt[b.charAt(i) - 'a']--;
        }
        for (int c : cnt) if (c != 0) return false;
        return true;
    }

    // 3) Grouping by canonical key.
    static List<List<String>> groupBySignature(String[] words) {
        Map<String, List<String>> groups = new HashMap<>();
        for (String w : words) {
            groups.computeIfAbsent(signature(w), k -> new ArrayList<>()).add(w);
        }
        return new ArrayList<>(groups.values());
    }

    static String signature(String w) {
        int[] cnt = new int[26];
        for (char c : w.toCharArray()) cnt[c - 'a']++;
        return Arrays.toString(cnt);                       // equal for equivalent items
    }

    // 4) Count pairs on the fly: each element pairs with all earlier equal ones.
    static long countEqualPairs(int[] nums) {
        Map<Integer, Integer> freq = new HashMap<>();
        long pairs = 0;
        for (int x : nums) {
            int before = freq.getOrDefault(x, 0);
            pairs += before;
            freq.put(x, before + 1);
        }
        return pairs;
    }
}

Complexity

Operation Average Worst case
HashMap / HashSet get, put, contains O(1) O(log n) per bucket in Java 8+ (treeified), O(n) in theory
One pass with lookups O(n) time, O(n) space
int[26] frequency table O(n) time, O(1) space (alphabet is fixed)
Group by sorted key O(n · k log k)
Group by count-vector key O(n · k)

Note the space claim interviewers like to probe: a counter over lowercase letters is O(1), not O(n), because its size is bounded by the alphabet.

Variations

  • Complement lookup: store what you've seen, search for what you need. Two Sum.
  • Membership / duplicate detection: a set, or a map of last index for distance limits. Contains Duplicate, Contains Duplicate II.
  • Frequency comparison: count one side, consume with the other. Valid Anagram, Ransom Note, Maximum Number of Balloons.
  • Bijection checks: two maps (or two arrays) enforce one-to-one mapping. Isomorphic Strings.
  • Canonical grouping: signature key to bucket. Group Anagrams.
  • Set-based sequence building: start only at sequence heads. Longest Consecutive Sequence.
  • Greedy with counts: Reorganize String (most frequent first), Split Array into Consecutive Subsequences (count map plus "needs" map).
  • Implementing the structure itself: buckets and chaining. Design HashMap.

Common mistakes

  • Inserting before checking in Two Sum, so nums[i] pairs with itself when target == 2 * nums[i].
  • Using a HashMap<Character,Integer> when an int[26] would be faster, simpler and O(1) space.
  • Using a mutable object (like an int[] or a List you keep changing) directly as a map key; arrays hash by identity, so convert to a String or immutable key.
  • Forgetting getOrDefault or merge, causing a NullPointerException when unboxing a missing count.
  • Assuming hash maps preserve insertion order; use LinkedHashMap if order matters.
Warning

Never compare two counts fetched from a Map<…, Integer> with ==. Java caches boxed integers only from -128 to 127, so map.get(a) == map.get(b) passes small tests and then fails once a count reaches 128. Use .equals() or unbox to int first.

All practice problems

16 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.

My notes saved in this browser