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 — 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.
| i | a[i] | need | map before lookup | Hit? | then put |
|---|---|---|---|---|---|
| 0 | 2 | 7 | empty | no | 2 → 0 |
| 1 | 7 | 2 | {2: 0} | yes, j = 0 | return [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.
| i | a[i] | need | idx.get(need) | map after this i | Why |
|---|---|---|---|---|---|
| 0 | 3 | 3 | null | {3: 0} | must not match i with i; put happens after the miss |
| 1 | 3 | 3 | 0 | return [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.
- Putting
a[i]before the lookup.[3, 3]target 6 returns[0, 0]. HashSetof values when they asked for indices.- Using
target - a[i]with ints that overflow — for this booklet the values are small; say you noticed.
| Problem | Pattern | Links |
|---|---|---|
| Pair sum | Unsorted → HashMap; sorted → two pointers | clue |
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.
| Word | Sorted key | Map after insert |
|---|---|---|
| eat | aet | aet → [eat] |
| tea | aet | aet → [eat, tea] |
Answer: one group [eat, tea].
Example 2 — every step
eat, tea, tan, ate, nat, bat. Watch the map fill.
| Word | Key | aet | ant | abt |
|---|---|---|---|---|
| eat | aet | [eat] | — | — |
| tea | aet | [eat, tea] | — | — |
| tan | ant | [eat, tea] | [tan] | — |
| ate | aet | [eat, tea, ate] | [tan] | — |
| nat | ant | [eat, tea, ate] | [tan, nat] | — |
| bat | abt | [eat, tea, ate] | [tan, nat] | [bat] |
Answer: three groups — [eat, tea, ate], [tan, nat], [bat].
- 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.
| Problem | Pattern | Links |
|---|---|---|
| Group anagrams | Sorted or count[26] key | cheat card · R4 coding |
| Find anagrams variation (window) | Fixed window of letter counts, not grouping | Java 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).
| x | x−1 in set? | Action | Run length |
|---|---|---|---|
| 1 | 0 missing → head | walk 1,2,3 | 3 |
| 2 | 1 present → skip | — | — |
| 3 | 2 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.
| x | contains(x−1)? | Head? | Walk | Length | best |
|---|---|---|---|---|---|
| 100 | 99 no | yes | 100 only | 1 | 1 |
| 4 | 3 yes | no — skip | — | — | 1 |
| 200 | 199 no | yes | 200 only | 1 | 1 |
| 1 | 0 no | yes | 1,2,3,4 | 4 | 4 |
| 3 | 2 yes | no — skip (1 is the head) | — | — | 4 |
| 2 | 1 yes | no — 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.
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.
- 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.
| Problem | Pattern | Links |
|---|---|---|
| Loyal customer logs | id → days / set of dates | Java in ch.05 · cheat card |
| Logger message global | message → last print time | Java in ch.05 · cheat card |
| Top-k frequent | count map + size-k heap | Java in ch.05 · cheat card · clue · clue |
| k-page visit sequence | Title named; extra IO unnamed — window + map | cheat card |
| Most frequent 3-page sequence | Consecutive triples per user, then a global map — hashing, not a stack | R4 coding |