LC Patterns — Hash Map / Hash Set

Siblings: probability, linked-lists, … · Code: C++
Per-pattern template: What it is → Intuition → Approach → Challenges → LC problems → Worked examples

◆ The Master Insight

A hash map is a deal: spend memory, and in return any "have I seen this? / where is it? / how many are there?" question is answered instantly, no matter how much data you've stored.

The slow version of these problems is always "for each element, scan everything to find its partner / group / count" — O(n²). The hash map flips the direction: as you walk the data once, store each item under the key a future item would search for. Every lookup is O(1), and the whole thing is one pass. So the real skill is designing the key — the value is usually boring (an index, a count, a list); the key is where the thinking happens.

Recognition trigger: "find two things that pair up", "count how many", "group these together", "does a duplicate exist", "first unique", "subarray summing to k", any O(n²) brute force whose inner loop is a search — plus design problems demanding O(1) get/put. Counter-trigger: if data must stay sorted or you need "closest / next larger", a hash map has no order — that's binary search, heaps, or sorted containers.
Two habits for interviews. (1) Say the trade out loud: "I'll trade O(n) space for O(1) lookups, dropping O(n²) to O(n)." (2) Say what key and value mean before coding: "the map goes from running total to the first index where it happened." A map with an unclear meaning is where bugs breed.

§0Mechanics & Universal Pitfalls

C++ · the toolbox
unordered_map<int,int> d;          // key → value (average O(1))
++d[k];                            // operator[] default-inits 0 — counting in one line
d.count(k);                        // membership test (0 or 1)
auto it = d.find(k);               // lookup; it == d.end() means absent
unordered_set<int> seen;           // membership only, no values
int cnt[26] = {0};                 // for lowercase letters, a plain array beats a map
for (char c : s) ++cnt[c - 'a'];   // frequency count
string key = w;                    // anagram key = a sorted copy of the word
sort(key.begin(), key.end());

Facts to have loaded

unordered_map keys need a hash + operator==. int and string work out of the box; pair/tuple have no default hash — encode them into one value (e.g. r*1000 + c or a joined string) or fall back to std::map (ordered, O(log n)). O(1) is the average — worst case O(n) with bad collisions; say "expected O(1)" once. unordered_map has no order at all — for "first unique," rescan the original string. Anagram test: equal 26-count arrays, or sort both copies. Reach for unordered_set whenever you'd only ever store true. Use reserve() to cut rehash cost when the size is known.

Universal pitfalls

1. Check before vs after insert. Two Sum works because you check for the partner first, then insert yourself — so you never pair an element with itself. Decide the order consciously every time.

2. No default hash for pair/tuple. unordered_map<pair<int,int>,…> won't compile. Encode composite keys into one int/long long/string, or use std::map.

3. Assuming order. unordered_map has no order; "give me the smallest key" is O(n). Need order? std::map (O(log n)).

4. Keys must capture everything that matters. If two things get the same key but shouldn't be grouped, the key is missing information.

5. Decrement-below-zero. When consuming counts, check cnt < 0 right after --cnt (a single count array handles supply-vs-demand in one pass).

6. Small dense integer keys? A plain list indexed by value is simpler and faster (leads to Pattern 8, where the input array itself becomes the map).

1Have I Seen It? ★ start here

What it is: The simplest deal: walk the data once, keep a set/map of what you've seen, and for each new element ask one O(1) question about the past — "has this exact thing appeared?" or "has my partner appeared?" (the partner being whatever makes the pair work, like target − x).

Two Sum (LC 1) — nums = [2, 7, 11, 15], target 9. Ask first, store second.
array
2
0
7
1
11
2
15
3
see xpartner = 9 − xin map?action
27store {2:0}
72yes → idx 0return [0, 1]
map value→index
20
order matters: ask for the partner, then store yourself → a lone 3 never pairs with itself, but [3,3] works.

Intuition: the brute force scans backward for every element — O(n²). But that backward scan asks a question with a precomputable answer. So precompute it: store each element as you pass. Two Sum (1): partner is target − x, looked up before storing x. Contains Duplicate (217): question is "seen exactly this value?" 219 adds "…within the last k positions?" — so the stored value becomes the last index each value appeared, and you compare distances.

C++ · Two Sum (LC 1)
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int,int> seen;                 // value → index
    for (int i = 0; i < (int)nums.size(); ++i) {
        auto it = seen.find(target - nums[i]);   // ask for the partner FIRST
        if (it != seen.end()) return {it->second, i};
        seen[nums[i]] = i;                       // then store yourself
    }
    return {};
}

Challenges

The check-then-insert order (self-pairing — the bug of this pattern); returning indices vs values (Two Sum wants indices → store value→index); duplicates in Two Sum ([3,3] works because you check before inserting the second 3 — trace it once); in 219, updating the stored index even on no match (stale indices give wrong distances); the sorted-input alternative is two pointers — the map is for the unsorted case.

LC 1 Two SumLC 217 Contains DuplicateLC 219 Contains Duplicate II
Example 2 · Contains Duplicate II (LC 219) — nums = [1,2,3,1], k = 3
  • Map holds each value's most recent index. Index 0,1,2: store 1→0, 2→1, 3→2.
  • Index 3: 1 is in map at index 0; distance 3 − 0 = 3 ≤ kTrue.
  • If a value recurs later, overwrite its index — only the latest occurrence can be close enough.
"The inner loop of the brute force is a search — I'll replace it with a hash lookup: O(n) space buys O(n) total time."

2Frequency Counting (the count-array reflex)

What it is: Count how many times each thing appears, then answer questions off the counts: can these letters build that word? which character appears exactly once first? sort characters by how common they are. One counting pass (a size-26 array or an unordered_map), then a second pass over the counts.

Ransom Note (LC 383) — note "aab" from magazine "aabb": spend from supply
supply = counts of magazine (a size-26 int array)
a:2
b:2
spend the note's letters, never dropping below 0
'a' →
a:1
'a' →
a:0
'b' →
b:1
one array: ++cnt over the magazine, --cnt over the note — if any drops < 0, demand beat supply. Here it never does → true.
First Unique Character (LC 387) — s = "leetcode": count, then scan for first count 1
l:1
idx 0 ✓
e:3
t:1
c:1
o:1
d:1
pass 1 counts everything; pass 2 walks in order → first char with count 1 is 'l' at index 0. Two passes are mandatory — you can't call a char unique until you've seen the whole string.

Intuition: many string/array questions aren't about order — only about supply. Ransom note (383) asks whether the magazine's supply covers the note's demand: count both, compare. First unique (387): a char is unique iff its total count is 1 — count first, then re-walk. Sort by frequency (451): count, then rebuild most-common-first. Second-level move: count the counts (LC 1207 asks if all frequencies are distinct → drop them in an unordered_set and check its size).

C++ · Ransom Note & Sort by Frequency
bool canConstruct(string note, string mag) {
    int cnt[26] = {0};
    for (char c : mag)  ++cnt[c - 'a'];              // supply
    for (char c : note) if (--cnt[c - 'a'] < 0)      // spend; demand beat supply?
        return false;
    return true;
}

string frequencySort(string s) {
    unordered_map<char,int> cnt;
    for (char c : s) ++cnt[c];
    vector<pair<int,char>> v;                          // (count, char)
    for (auto& [ch, c] : cnt) v.push_back({c, ch});
    sort(v.rbegin(), v.rend());                      // most common first
    string out;
    for (auto& [c, ch] : v) out += string(c, ch);
    return out;
}

Challenges

Trying supply-questions in one pass (387 genuinely needs count-then-scan — resisting premature cleverness is the skill); mutating counts while consuming; tie order among equal counts is up to your comparator (say it if output could vary); the majority element (169) has both a counting answer and the O(1)-space Boyer–Moore vote — offer counting first, name Boyer–Moore as the follow-up; keep the meaning crisp: "letter → how many I have left."

LC 383 Ransom NoteLC 387 First Unique CharacterLC 451 Sort Characters by Frequency

3Count Pairs by Complement (add-then-count streaming)

What it is: Count how many pairs satisfy a relation — sum divisible by 60, difference exactly k, equal values. Like Pattern 1, but instead of stopping at the first partner you add the number of partners seen so far to a running answer, then register yourself. Every pair is counted exactly once, no index juggling.

Songs Divisible by 60 (LC 1010) — times [30,20,150,100,40] → remainders [30,20,30,40,40]
rneeds (60−r)%60count seen+ ansrunning total
30300+00
20400+00
30301+11
40201+12
40201+13
answer 3 → pairs (30,150), (20,100), (20,40). "needs" is (60 − r) % 60 so r=0 correctly needs 0, not 60.

Intuition: for each element, the number of pairs it closes is exactly "how many earlier elements were my partner" — a count the map already has. The order — count first, then add yourself — guarantees each pair is counted once (as "later meets earlier") and never self-paired. Difference k (2006): partners of x are earlier x−k and x+k — look both up. Good pairs (1512): partner is an earlier equal x.

C++ · Count pairs streaming (LC 1010)
int numPairsDivisibleBy60(vector<int>& time) {
    long long ans = 0;               // n² pairs can overflow int → use long long
    int count[60] = {0};             // remainder → how many seen
    for (int t : time) {
        int r = t % 60;
        ans += count[(60 - r) % 60]; // add partners seen so far, FIRST
        ++count[r];                  // then register yourself
    }
    return ans;
}

Challenges

Self-partnered classes (r=0 pairs with r=0; r=30 with r=30 — add-then-count handles them, but verify on a tiny case); flipping the order (count yourself first → every element self-pairs once); difference problems need two lookups (only checking x−k misses half; the streaming discipline sorts this out); overflow of pair counts in other languages (mention long); the closed-form alternative is sum of C(c,2) over groups.

LC 1010 Pairs of Songs ÷60LC 2006 Pairs With Abs Diff KLC 1512 Number of Good Pairs
Example 2 · Good Pairs (LC 1512) — nums = [1,1,1,2]
  • Partner of x is an earlier equal x. First 1: +0. Second 1: +1. Third 1: +2. The 2: +0.
  • Answer 3 — same as C(3,2)=3 for the group of three 1s. The stream and the formula always agree.
"I count each pair once, at its later element: add how many partners came before me, then register myself."

4Group by Canonical Key (make equals collide)

What it is: Things belong together when they share a hidden signature — anagrams share letters, matrix rows can equal columns, a Sudoku digit "collides" in the same row/column/box. Design a canonical key — one standardized form that all group members map to and nothing else does — and let the map do the grouping.

Group Anagrams (LC 49) — sorted-letters key collides equivalent words
word → key
eat → aet
tea → aet
tan → ant
ate → aet
nat → ant
bat → abt
unordered_map<string,vector>
buckets
aet
eat, tea, ate
ant
tan, nat
abt
bat
no word is ever compared to another word — the key does all the "which group?" work. Faster key: a 26-char count string (O(L) vs O(L log L) to sort).
Valid Sudoku (LC 36) — three rules become one: "no signature twice"
5
3
.
6
5
.
.
9
8
encoded keys
unordered_set<string> of "kind index digit"
"r0#5"insert
"r0#5"✗ already in!
each filled cell emits three strings "r"+r, "c"+c, "b"+box (each with the digit). C++ has no default hash for a tuple, so encode the signature into a string. A repeat is the rule violation → invalid. box = (r/3)*3 + c/3.

Intuition: grouping is pairwise comparison in disguise — O(n²) if you compare everything with everything. A canonical key makes comparison free: two things share a group iff their keys are equal. Anagrams (49): sorted letters. Equal row-column pairs (2352): key = the row joined into a string (a vector has no default hash). Valid Sudoku (36): the same pattern used for collision-detection instead of collection.

C++ · Group Anagrams (LC 49)
vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> groups;
    for (auto& w : strs) {
        string key = w;
        sort(key.begin(), key.end());        // canonical key = sorted letters
        groups[key].push_back(w);
    }
    vector<vector<string>> out;
    for (auto& [k, v] : groups) out.push_back(move(v));
    return out;
}

Challenges

A key missing information (grouping rows by their sum collides unequal rows — the ⇐ direction fails); a key with extra information (including position in an anagram key — nothing groups); unhashable composite keys (a pair/vector in an unordered_map is a compile error — encode to a string/long long); the sorted-string vs count-string trade-off in 49 (say the complexities); in 36, one unordered_set<string> of encoded signatures keeps it uniform.

LC 49 Group AnagramsLC 36 Valid SudokuLC 2352 Equal Row and Column Pairs
"Two items should get the same key if and only if they belong together — let me check my key in both directions before coding."

5Two-Way Mapping (bijection checks)

What it is: Check that two sequences match up one-to-one: every 'a' in s maps to the same letter in t, and no two different letters map to the same target. One map is not enough — you need both directions (or a trick that implies the second).

Isomorphic Strings (LC 205) — s = "egg", t = "add": consistent both ways → True
s → t
ea
gd
t → s
ae
dg
the counterexample that kills one-map solutions → s = "ab", t = "cc"
s → t (looks fine)
ac
bc
t → s (conflict!)
ca
cb ✗
forward map alone says True; the backward map catches c mapping to two letters → False. That's why you need both.

Intuition: a single map s→t catches "one letter mapping to two things" but misses "two letters mapping to the same thing." Isomorphic (205) and word pattern (290 — same problem with words) both need the backward map: check and update both every step. Find-and-replace pattern (890): normalize both to a "first-occurrence fingerprint" ("abb" → [0,1,1], "mee" → [0,1,1]) and compare — normalizing to a canonical form quietly is the two-way check, connecting back to Pattern 4.

C++ · Isomorphic Strings (LC 205)
bool isIsomorphic(string s, string t) {
    unordered_map<char,char> s2t, t2s;
    for (int i = 0; i < (int)s.size(); ++i) {
        char a = s[i], b = t[i];
        if (s2t.count(a) && s2t[a] != b) return false;   // conflict forward
        if (t2s.count(b) && t2s[b] != a) return false;   // conflict backward
        s2t[a] = b; t2s[b] = a;
    }
    return true;
}

Challenges

Shipping only the forward map (the "cc" counterexample — memorize it, it's the whole pattern; volunteer it before the interviewer asks); 290's length mismatch edge ("aaa" vs two words — check lengths first); updating maps before checking (order the conflict check first); recognizing the family — "follows the same pattern" or "isomorphic" mean bijection, so two maps or a fingerprint.

LC 205 Isomorphic StringsLC 290 Word PatternLC 890 Find and Replace Pattern
Example 2 · Word Pattern (LC 290) — pattern "abba", s = "dog cat cat dog"
  • Split s → [dog, cat, cat, dog]. Lengths match (4 vs 4) — check first.
  • a→dog, b→cat (both directions). Then b→cat ✓, a→dog ✓ → True.
  • With "dog cat cat fish": final a→fish conflicts with a→dog → False.

6Prefix Sum + Hash Map ★ most-asked medium in this file

What it is: Count or find subarrays with a sum property — sum exactly k, equal 0s and 1s, sum divisible by k. A subarray sum is a difference of two running totals (sum(i..j) = prefix[j] − prefix[i−1]), so "find a subarray summing to k" becomes "find two running totals that differ by k" — Pattern 1's complement lookup, applied to prefix sums.

Subarray Sum Equals K (LC 560) — nums = [1,2,3], k = 3. Seed {0:1}.
after xtotallook for total − kseen?ansrecord
seed00{0:1}
111 − 3 = −20{0:1, 1:1}
233 − 3 = 0yes (×1, the seed!)1+ {3:1}
366 − 3 = 3yes (×1)2+ {6:1}
answer 2 → subarrays [1,2] and [3]. The {0:1} seed did real work — without it, the subarray starting at index 0 is missed (the bug of this pattern).

Intuition: at position j you want earlier positions i where prefix[j] − prefix[i] = k, i.e. earlier totals equal to prefix[j] − k — a map from total → count answers that in O(1) (560). Equal 0s/1s (525): recode 0 as −1, so it becomes "sum = 0," and for the longest such subarray store the first index of each total. Divisible by k (974): two prefixes work iff same remainder mod k → key = remainder. Seed the empty prefix: {0:1} for counting, {0:-1} for first-index.

C++ · counting template (560) & longest template (525)
int subarraySum(vector<int>& nums, int k) {          // 560 — count
    unordered_map<long long,int> count{{0, 1}};      // seed the empty prefix
    long long total = 0; int ans = 0;
    for (int x : nums) {
        total += x;
        auto it = count.find(total - k);            // lookup BEFORE insert
        if (it != count.end()) ans += it->second;
        ++count[total];
    }
    return ans;
}

int findMaxLength(vector<int>& nums) {               // 525 — longest, store first index
    unordered_map<long long,int> first{{0, -1}};
    long long total = 0; int best = 0;
    for (int i = 0; i < (int)nums.size(); ++i) {
        total += nums[i] ? 1 : -1;
        auto it = first.find(total);
        if (it != first.end()) best = max(best, i - it->second);
        else first[total] = i;                      // never overwrite — earliest wins
    }
    return best;
}

Challenges

The {0:1} / {0:-1} seed (the bug — test on an array whose answer starts at index 0, like [k]); overwriting first-occurrence when you needed earliest (525) vs counting all (560) — the two templates differ in exactly one line; lookup before insert; why sliding window fails here (negatives break grow/shrink — a favorite follow-up); remainder-key problems (974) apply mod to the running total — and C++ % can be negative, so normalize with ((total % k) + k) % k.

LC 560 Subarray Sum Equals KLC 525 Contiguous ArrayLC 974 Subarray Sums Divisible by K
"Subarray sums are differences of prefix sums, so I'm looking for a matching earlier prefix — a hash map makes that O(1), seeded with the empty prefix so subarrays starting at index 0 aren't lost."

7Seen-States: sequence walks, cycles, fingerprints

What it is: The "have I seen it?" set applied to bigger things than single values: whole positions in a process (to detect a loop), stretches of consecutive numbers (to walk chains), or chunks of a string (to spot repeats). The set answers "am I revisiting?" — each problem differs in what a "state" is.

Longest Consecutive Sequence (LC 128) — nums = [100, 4, 200, 1, 3, 2]
dump in a set; only a start (no predecessor) walks forward
100
start, len 1
200
start, len 1
1
start ↓
2
walk
3
walk
4
walk
1 is a start (0 absent) → walk 1·2·3·4 = length 4. 2,3,4 have predecessors → skipped as starts. Each number visited ≤ twice → O(n) despite the nested-looking loop.
Happy Number (LC 202) — n = 2: sum of squared digits until 1, or a loop
2 4 16 37 58 89 145 42 20 4↺ seen!
4 reappears → the chain loops and never reaches 1 → not happy. Bounded states + pigeonhole ⇒ it must repeat. Floyd's cycle detection is the O(1)-space upgrade.

Intuition: 128: a number x is a start iff x−1 is missing — only then walk forward. The "only start at starts" gate makes it O(n) despite the loop, and the O(n) proof is the sentence interviewers wait for. 202: store every total seen; a revisit means a loop. 187 (repeated DNA): slide a length-10 window, store each chunk; a chunk already present is a repeat (report each once).

C++ · Longest Consecutive Sequence (LC 128)
int longestConsecutive(vector<int>& nums) {
    unordered_set<int> s(nums.begin(), nums.end());
    int best = 0;
    for (int x : s) {
        if (s.count(x - 1)) continue;      // not a start — skip (keeps it O(n))
        int y = x;
        while (s.count(y + 1)) ++y;        // walk the run
        best = max(best, y - x + 1);
    }
    return best;
}

Challenges

128's complexity claim ("isn't the while inside the for O(n²)?" — verbatim answer: "the inner walk only runs from starts, and each number belongs to exactly one run, so total inner work is O(n)"); duplicate reports in 187 (a second "already reported" set); 202's termination (bounded values ⇒ pigeonhole ⇒ reach 1 or loop); sorting for 128 (O(n log n) works — name it, then beat it).

LC 128 Longest ConsecutiveLC 202 Happy NumberLC 187 Repeated DNA Sequences

8The Array as Its Own Hash Map (in-place index marking)

What it is: When values are (or can be brought) into 1..n and the array has length n, the array itself is the hash map: value v ↔ index v−1, and "I've seen v" is recorded by marking that slot (negating it, or swapping v into it). O(1) extra space — the trick behind every "find the missing/duplicate in O(n) time, O(1) space."

Find All Duplicates (LC 442) — negate slot v−1; already-negative = duplicate
nums = [4, 3, 2, 7, 8, 2, 3, 1] — process 4,3,2,7,8 (flip their home slots)
−4
0
−3
1
−2
2
−7
3
−8
4
2
5
3
6
−1
7
then see 2 → slot 1 is already negative → 2 is a duplicate; see 3 → slot 2 already negative → duplicate. Answer [2, 3]. The signs were the hash set.
negated = "this value seen"
First Missing Positive (LC 41) — cyclic sort: send value v to slot v−1, then scan
nums = [3, 4, −1, 1] → after homing
1
✓ 1
−1
want 2
3
✓ 3
4
✓ 4
first slot i where nums[i] != i+1 → slot 1 holds −1, expected 2 → answer 2. Each swap homes one value for good → ≤ n swaps → O(n).

Intuition: a hash map from {1..n} to anything is just an array of size n — and you already have one. Marking by negation (448, 442): flip nums[v−1] negative; still-positive at the end means that number never appeared (448); already-negative means seen twice (442). Marking by swapping into place (41): repeatedly swap each value into its home slot ("cyclic sort"), then the first misplaced slot reveals the answer.

C++ · negation (442) & cyclic sort (41)
vector<int> findDuplicates(vector<int>& nums) {        // 442
    vector<int> res;
    for (int x : nums) {
        int i = abs(x) - 1;                          // abs() mandatory — value may already be flipped
        if (nums[i] < 0) res.push_back(i + 1);       // already marked → duplicate
        else nums[i] = -nums[i];
    }
    return res;
}

int firstMissingPositive(vector<int>& nums) {         // 41
    int n = nums.size();
    for (int i = 0; i < n; ++i)
        while (nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] != nums[i])
            swap(nums[i], nums[nums[i]-1]);          // guard stops infinite swap
    for (int i = 0; i < n; ++i)
        if (nums[i] != i + 1) return i + 1;
    return n + 1;
}

Challenges

Forgetting abs when indexing (a flipped value indexes a negative slot — crash or silent nonsense); the infinite-swap guard in 41 (compare nums[nums[i]-1] != nums[i], not != i+1 — trace [1,1]); mutating the input (state it); the off-by-one between value v and index v−1 (write the convention as a comment); trigger recognition — "O(n) time, O(1) space, values in 1..n" is this pattern with near-certainty.

LC 448 Find All DisappearedLC 442 Find All DuplicatesLC 41 First Missing Positive

9Hash Map in Design Problems (composition)

What it is: "Design a class with O(1) operations" — the answer is almost always a hash map plus one other structure for what the map can't do: an array for random access (380), sorted lists for time queries (981), heaps/lists for feeds (355), a doubly linked list for recency (146). The map's role is always the same — jump straight to the right spot in the other structure.

Insert Delete GetRandom O(1) (LC 380) — map + array, remove via swap-with-last
vals (array — random index = random element)
7
0 ← remove
3
1
9
2 (last)
after: copy last (9) into slot 0, fix its pos, pop
9
0
3
1
pos value→index
92 → 0
31
7deleted
invariant: pos[v] is always v's current index in vals. Nothing shifts → remove is O(1). The swap-pop idiom appears all over design problems.

Intuition: each structure is missing something. A map alone can't return a uniform random element (380) — but map + array can: the array holds elements (random index = random element), the map holds each value's position. Time-based KV (981): map key → append-only list of (timestamp, value); timestamps arrive increasing, so each list is sorted for free, and get is a binary search for "rightmost time ≤ t." Twitter (355): map user → tweets, user → followees; the feed merges recent tweets. LRU (146) = map + doubly linked list.

C++ · Insert Delete GetRandom O(1) (LC 380)
class RandomizedSet {
    vector<int> vals;                    // elements, tightly packed
    unordered_map<int,int> pos;          // value → its index in vals (the invariant)
public:
    bool insert(int v) {
        if (pos.count(v)) return false;
        pos[v] = vals.size(); vals.push_back(v); return true;
    }
    bool remove(int v) {
        auto it = pos.find(v);
        if (it == pos.end()) return false;
        int i = it->second, last = vals.back();
        vals[i] = last; pos[last] = i;   // move last into the hole, fix its pos
        vals.pop_back(); pos.erase(it);  // shrink
        return true;
    }
    int getRandom() { return vals[rand() % vals.size()]; }  // uniform over current set
};

Challenges

380's remove when the victim is the last element (the swap becomes a self-swap — must tolerate it; test removing the only element); forgetting to update the moved element's map entry after swap-pop (the #1 bug — the invariant sentence catches it); 981 assuming you must sort per query (timestamps arrive sorted — appending keeps order); scope creep in 355 ("collect recent tweets and sort" passes and is honest); the meta-skill: decompose ops → structures out loud before any code.

LC 380 Insert Delete GetRandomLC 981 Time Based Key-Value StoreLC 355 Design Twitter
Example 2 · Time Based Key-Value Store (LC 981)
  • Structure: unordered_map<string, vector<pair<int,string>>>. set just push_backs.
  • Timestamps only ever increase → every vector stays sorted with zero effort (the main observation).
  • get: binary-search the vector for the rightmost pair with time ≤ t (upper_bound, step back one). Before the first timestamp → return "".

Recognition Cheat Sheet

#PatternTrigger phraseThe key is…Canonical trap
1Have I seen it?two sum, duplicatesthe element (or its complement)check before insert
2Frequency counting"can build", "first unique", "most common"element → countone-pass temptation (387 needs two)
3Count pairs"how many pairs with ⟨relation⟩"partner signature (remainder, x±k)self-partnered classes (r = 0)
4Canonical key"group", anagrams, "same rows"standardized form (sorted, tuple)key missing info / unhashable list
5Two-way mapping"isomorphic", "follows pattern"both directionsone-map "cc" counterexample
6Prefix sum + map"subarray sum = k / divisible / balanced"running total (or its remainder)forgetting the {0:1} seed
7Seen statesconsecutive runs, loops, repeated chunksposition / total / substring128's O(n) justification
8Array as map"O(1) space, values in 1..n"value v ↔ index v−1abs() when indexing; swap guard
9Design combos"O(1) operations", "design"value → location in the other structureupdate the moved element after swap-pop

Study Order & The Interview Sentence

1 the core reflex → 24 key design starts → 3 streaming counting → 6 the interview medium (drill the seed until automatic) → 5789 design composition (pairs with the linked-list file's LRU)
"The brute force re-searches the past for every element — I'll store the past in a hash map keyed by ⟨what future elements look up⟩, trading O(n) space for O(1) lookups: ⟨n²⟩ becomes ⟨n⟩. The map means: ⟨key⟩ → ⟨value⟩; let me state that before coding."