Chapter 13 · Hashing

Have I seen this, and where

A HashMap turns a scan into a lookup: complement index, anagram signature, or “is n−1 already a number in the set.” Job 10454435 still none.

← Stacks & Queues · Arrays & Strings →

Two-sum HashMapStore the index. Presence-only misses the pair of duplicates.
Group anagramsSorted string as the key; list of words as the value.
Longest consecutive sequenceStart a run only at heads — skip if n−1 is already in the set.
HashMap vs HashSet Set answers “have I seen this value.” Map answers “where / how many / which group.” Two-sum needs the index. Three-page sequences need a count of triples. Job 10454435 still none.

Two-sum — HashMap of value → index

Recognize it from — two indices whose values add to a target, array unsorted, you may not reorder (or you must return original indices). If it is already sorted, two pointers from the ends are enough and you do not need a map.

What it actually does — as you walk left to right, ask “is target − a[i] already stored?” If yes, those two indices are the pair. If no, store a[i] → i so a later index can find you. You look up before you insert, so you never pair an index with itself. The map holds the last (or first — here, the only so far) index of each value, not a boolean.

Simple — read this first

int[] twoSum(int[] a, int target) {
    Map<Integer, Integer> idx = new HashMap<>(); // value → index, not a Set
    for (int i = 0; i < a.length; i++) {
        int need = target - a[i];
        if (idx.containsKey(need)) return new int[]{idx.get(need), i};
        idx.put(a[i], i);              // after the lookup, so i cannot match itself
    }
    return new int[]{-1, -1};
}

Interview — type this

int[] twoSum(int[] a, int target) {
    Map<Integer, Integer> idx = new HashMap<>();
    for (int i = 0; i < a.length; i++) {
        Integer j = idx.get(target - a[i]); // null if unseen — do not use contains then get
        if (j != null) return new int[]{j, i};
        idx.put(a[i], i);                   // duplicate values: later index overwrites; two-sum of 3+3 still works because we check before put
    }
    throw new IllegalArgumentException("no pair");
}
Example 1 — simple

a = [2, 7, 11], target = 9.

[2, 7, 11], t = 9
ia[i]needmap before lookupHit?then put
027emptyno2 → 0
172{2: 0}yes, j = 0return [0, 1]

Answer: [0, 1] (2 + 7). Index 2 is never visited.

Example 2 — every step

Duplicate values: a = [3, 3], target = 6. A Set of “presence” without indices cannot return [0, 1]. If you put before you look up, the first 3 matches itself.

[3, 3], t = 6 — store index, check before put
ia[i]needidx.get(need)map after this iWhy
033null{3: 0}must not match i with i; put happens after the miss
1330return [0, 1]the stored index is the other 3

Answer: [0, 1]. If the map had only stored true, you would know a 3 exists but not which two indices to return.

Time O(n) average Space O(n) Worst get O(n) collisions
Why a map of indices Nested loops are O(n²). Sorting then two-pointers is O(n log n) and loses original indices unless you keep pairs. The map is “have I seen the complement, and where.” Honest Big-O: average O(1) get; a pathological hash can chain. Java 8+ treeifies long colliding bins when keys are comparable — still not an excuse to skip the map.
Common mistakes
  • Putting a[i] before the lookup. [3, 3] target 6 returns [0, 0].
  • HashSet of values when they asked for indices.
  • Using target - a[i] with ints that overflow — for this booklet the values are small; say you noticed.
ProblemPatternLinks
Pair sumUnsorted → HashMap; sorted → two pointersclue

Group anagrams — sorted key

Recognize it from — words that are permutations of each other belong together. Sliding-window “find anagrams in a string” is a count-window, not this map-of-lists. Both use letter counts.

What it actually does — two words are anagrams if they have the same letters. Sorting the characters gives one canonical key (eat and tea both become aet). A map from that key to a list of original words fills as you scan. An int[26] count serialized as a key is the same idea without sorting each word (better when words are long and alphabet is tiny).

Simple — read this first

List<List<String>> groupAnagrams(String[] words) {
    Map<String, List<String>> groups = new HashMap<>();
    for (String w : words) {
        char[] c = w.toCharArray();
        Arrays.sort(c);                       // canonical key
        String key = new String(c);
        groups.computeIfAbsent(key, k -> new ArrayList<>()).add(w);
    }
    return new ArrayList<>(groups.values());
}

Interview — type this

List<List<String>> groupAnagrams(String[] words) {
    Map<String, List<String>> groups = new HashMap<>();
    for (String w : words) {
        int[] cnt = new int[26];
        for (int i = 0; i < w.length(); i++) cnt[w.charAt(i) - 'a']++;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 26; i++) {        // key from counts — no sort of the word
            sb.append('#').append(cnt[i]);    // '#' so 12,3 and 1,23 cannot collide
        }
        groups.computeIfAbsent(sb.toString(), k -> new ArrayList<>()).add(w);
    }
    return new ArrayList<>(groups.values());
}
Example 1 — simple

Words: eat, tea. Sorted key aet.

eat / tea
WordSorted keyMap after insert
eataetaet → [eat]
teaaetaet → [eat, tea]

Answer: one group [eat, tea].

Example 2 — every step

eat, tea, tan, ate, nat, bat. Watch the map fill.

Map after each word
WordKeyaetantabt
eataet[eat]——
teaaet[eat, tea]——
tanant[eat, tea][tan]—
ateaet[eat, tea, ate][tan]—
natant[eat, tea, ate][tan, nat]—
batabt[eat, tea, ate][tan, nat][bat]

Answer: three groups — [eat, tea, ate], [tan, nat], [bat].

Time O(n · L log L) sort key Count key O(n · L) Space O(n · L)
Why a map keyed by the signature Pairwise “is anagram?” is O(n² · L). The signature (sorted word or count[26]) puts all anagrams in one bucket. Find-anagrams-in-a-string is a sliding window of counts on one haystack — different card, same letter-count idea.
Common mistakes
  • Using the unsorted word as the key. Then nothing groups.
  • Serializing counts as "" + cnt[0] + cnt[1] without a delimiter — 12,3 vs 1,23.
  • Sorting the array of words globally. That is not grouping.
ProblemPatternLinks
Group anagramsSorted or count[26] keycheat card · R4 coding
Find anagrams variation (window)Fixed window of letter counts, not groupingJava in ch.05 · cheat card

Longest consecutive sequence — start only at heads

Recognize it from — longest run of consecutive integers (order in the array does not matter; duplicates usually ignored). Not a subarray (that would be sliding window / kadane). Not “consecutive in index.”

What it actually does — put every number in a HashSet. A number n is the head of a run only if n − 1 is missing. From each head you walk n, n+1, n+2, … while those values exist, and you remember the longest walk. Numbers that are not heads are skipped in O(1) — that is why the nested walk is still O(n): each value is visited from a head at most once.

Simple — read this first

int longestConsecutive(int[] a) {
    Set<Integer> s = new HashSet<>();
    for (int x : a) s.add(x);
    int best = 0;
    for (int x : s) {
        if (s.contains(x - 1)) continue; // not a head — a longer run starts earlier
        int y = x, len = 0;
        while (s.contains(y)) { y++; len++; } // walk forward only from a head
        best = Math.max(best, len);
    }
    return best;
}

Interview — type this

int longestConsecutive(int[] a) {
    if (a == null || a.length == 0) return 0;
    Set<Integer> s = new HashSet<>();
    for (int x : a) s.add(x);                 // duplicates collapse
    int best = 0;
    for (int x : s) {
        if (s.contains(x - 1)) continue;      // skip 2, 3, … when 1 is present
        int y = x;
        while (s.contains(y + 1)) y++;        // y is the last of this run
        best = Math.max(best, y - x + 1);
    }
    return best;
}
Example 1 — simple

[1, 2, 3]. Only 1 is a head (0 is not in the set).

[1, 2, 3]
xx−1 in set?ActionRun length
10 missing → headwalk 1,2,33
21 present → skip——
32 present → skip——

Answer: 3.

Example 2 — every step

[100, 4, 200, 1, 3, 2]. You skip 2 and 3 because 1 is the head of that run. You skip 4 for the same reason.

set = {1, 2, 3, 4, 100, 200} runs: 1-2-3-4 (len 4) 100 (len 1) 200 (len 1)
Every x in the set — skip non-heads
xcontains(x−1)?Head?WalkLengthbest
10099 noyes100 only11
43 yesno — skip——1
200199 noyes200 only11
10 noyes1,2,3,444
32 yesno — skip (1 is the head)——4
21 yesno — skip (1 is the head)——4

Answer: 4. Iteration order of a HashSet is not insertion order — the table is one possible order; every order still skips 2, 3, 4 and scores 4 from head 1.

Time O(n) average Space O(n)
Why start only at heads If you walk forward from every number, 1-2-3-4 is scanned from 1, from 2, from 3, from 4 — quadratic. The contains(x-1) skip means each run is expanded once. Sorting then a linear scan is O(n log n) and also correct; the set is the O(n) average version.
Common mistakes
  • Walking from every x, not only heads. Hidden O(n²).
  • Treating this as a contiguous subarray. Consecutive values, not consecutive indices.
  • Forgetting duplicates: the set collapses them so they do not inflate the length.

No named-IE card for this exact title in the notebook. Do not invent a Job 10454435 prompt or an LC id for it.

See also on this topic

Same “one pass, have I seen this” family. Most-frequent three-page sequence is consecutive triples per user, then a global map — not a cartesian across users. Logger is a map of message → last timestamp. Loyal-customer logs is a map of id → days seen. Top-k frequent is a map of counts then a heap (heap chapter also). k-page visit is a window plus a map; lock the unnamed IO on that card.

ProblemPatternLinks
Loyal customer logsid → days / set of datesJava in ch.05 · cheat card
Logger message globalmessage → last print timeJava in ch.05 · cheat card
Top-k frequentcount map + size-k heapJava in ch.05 · cheat card · clue · clue
k-page visit sequenceTitle named; extra IO unnamed — window + mapcheat card
Most frequent 3-page sequenceConsecutive triples per user, then a global map — hashing, not a stackR4 coding

← Stacks & Queues · Arrays & Strings →