Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window
P0EasyMental model~2 min speedrun

Hashing & Frequency Maps (Group Anagrams, Two Sum)

Grows into
Hash-Set Sequence Scanning
Unlocks
Fixed WindowTrieMost Frequent FirstLRU CachePrefix Sum MapInsert Delete GetRandomCanonical KeyBucket SortKMPConstruct Binary Tree
The problem

Group Anagrams (LC 49)(opens leetcode.com)

You get a list of words strs, all written in lowercase letters. Two words are anagrams of each other when you can reorder the letters of one to spell the other, so they use exactly the same letters, each the same number of times: "eat" and "tea" are anagrams, "aab" and "abb" are not.

Split strs into groups so that each group holds every word that is an anagram of the others in it, and return the list of groups. A word that has no anagram in the list forms a group on its own, and a word that appears twice is listed twice. The groups can come in any order, and so can the words inside each group.

Example 1
Input:strs = ["eat","tea","tan","ate","nat","bat"]
Output:[["bat"],["nat","tan"],["ate","eat","tea"]]
"ate", "eat" and "tea" all sort to "aet"; "nat" and "tan" both sort to "ant"; nothing else sorts to "abt", so "bat" stands alone.
strs
eat0tea1tan2ate3nat4bat5
key "aet"
eat0tea1ate2
key "ant"
tan0nat1
key "abt"
bat0

Hash Map

items ➔ computed key ➔ dict lookup or count in O(1)
Spot it when: The question is "have I seen this before?", "how many of each?" or "which items belong together?": duplicates, counts, a partner that completes a target, or items grouped by a key you compute from them.
Hash Map
Python
1from collections import defaultdict
2 
3 
4def group_anagrams(strs: list[str]) -> list[list[str]]:
5 """Group Anagrams: words with the same sorted letters share one key in a hash map.
6 Time: O(N * K log K) | Space: O(N * K)
7 """
8 groups: dict[str, list[str]] = defaultdict(list) # key -> every word read so far with that key
9 # 1. File Each Word Under Its Key:
10 # Words with the same letters and the same counts produce the same sorted-letters key ("eat", "tea" -> "aet"), so every anagram lands in the same group without comparing any two words.
11 for s in strs:
12 key = "".join(sorted(s))
13 groups[key].append(s)
14 return list(groups.values())
15 
16 
17def two_sum(nums: list[int], target: int) -> list[int]:
18 """Two Sum: remember each value's index, and look up the partner each new value needs.
19 Time: O(N) | Space: O(N)
20 """
21 index_of: dict[int, int] = {} # value -> an index where it was seen
22 # 1. Look Up The Partner Before Storing:
23 # If target - num was already seen, its stored index completes the pair; storing num only after the lookup keeps num from ever pairing with itself.
24 for i, num in enumerate(nums):
25 partner = target - num
26 if partner in index_of:
27 return [index_of[partner], i]
28 index_of[num] = i
29 return [] # LeetCode guarantees exactly one answer, so this line is never reached
Invariant: After each item, the map holds every item read so far under its exact key: groups[key] is precisely the words so far whose sorted letters equal key.

A hash map finds a key's entry in one step, so pick the key the question is about and store under it what the answer needs: in group_anagrams the key is a word's sorted letters and the value is every word read so far with that key; in two_sum the key is a number and the value is its index. Items that belong together produce equal keys, so groups[key].append(s) files every anagram in one pass without comparing any two words, and partner in index_of finds an earlier number that completes the pair without a second loop. The trap is a lossy key: it must be equal only for items that truly belong together, so key on "".join(sorted(s)), which keeps repeated letters, never on frozenset(s) or set(s), which drop them and put "aab" and "abb" in one group.

See it run

Count and Lookup, step by step

key - · groups 0

key aab · groups 0

key aab · groups 1

key aab · groups 1

key aet · groups 1

key aet · groups 2

key abb · groups 2

key abb · groups 3

key aet · groups 3

key aet · groups 3

key aab · groups 3

key aab · groups 3

key aet · groups 3

key aet · groups 3

key - · groups 3 · answer aab,baa | eat,tea,ate | abb

The top row is strs. The middle row is the key of each word. The bottom row is the number of the group that holds the word.

  • read the word being read from strs
  • key the sorted letters of that word
  • filed the group number the word was added to

Start: groups is empty and strs has 6 words. Each word will get a key and a group number.

key = "".join(sorted("aab")) = "aab".

groups["aab"] did not exist, so it starts as a new group 1 holding "aab".

Trap: frozenset("aab") and frozenset("abb") are both the letters "ab". A set key would put these two words in one group, but they are not anagrams.

key = "".join(sorted("eat")) = "aet".

groups["aet"] did not exist, so it starts as a new group 2 holding "eat".

key = "".join(sorted("abb")) = "abb".

groups["abb"] did not exist, so it starts as a new group 3 holding "abb".

key = "".join(sorted("tea")) = "aet".

groups["aet"] is group 2, so "tea" joins it. No other word was compared.

key = "".join(sorted("baa")) = "aab".

groups["aab"] is group 1, so "baa" joins it. No other word was compared.

key = "".join(sorted("ate")) = "aet".

groups["aet"] is group 2, so "ate" joins it. No other word was compared.

Done: 3 groups, aab,baa | eat,tea,ate | abb.

strs
0aab
1eat
2abb
3tea
4baa
5ate
key
group
1 / 15
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Keying on frozenset(s) instead of the sorted letters

    Mistake
    key = frozenset(s)
    Fix
    key = "".join(sorted(s))

    What happens: A set keeps each letter once and drops the repeats. "aab" and "abb" both give the set of a and b. They land in one group, but they are not anagrams. "".join(sorted(s)) keeps every letter, repeats included.

    Example: strs = ["aab", "abb"]
    Mistake: returns [['aab', 'abb']]Fix: returns [['aab'], ['abb']]
  2. 2

    Gotcha 2: Using the list from sorted(s) as the key

    Mistake
    key = sorted(s)
    Fix
    key = "".join(sorted(s))

    What happens: sorted(s) returns a list. A list can change, so Python does not allow it as a dict key. groups[key] raises a TypeError. Join the letters into a string, or wrap the list in tuple(...).

    Example: strs = ["eat", "tea"]
    Mistake: raises TypeErrorFix: returns [['eat', 'tea']]
  3. 3

    Gotcha 3: Storing num before looking up its partner

    Mistake
    partner = target - num
    index_of[num] = i
    if partner in index_of:
        return [index_of[partner], i]
    Fix
    partner = target - num
    if partner in index_of:
        return [index_of[partner], i]
    index_of[num] = i

    What happens: index_of[num] = i makes num visible to the lookup. If it runs first, partner in index_of can find num itself when partner == num. Then two_sum pairs one element with itself.

    Example: nums = [3, 2, 4], target = 6
    Mistake: returns [0, 0]Fix: returns [1, 2]
  4. 4

    Gotcha 4: Joining letter counts into a key without a separator

    Mistake
    key = "".join(map(str, count))
    Fix
    key = tuple(count)

    What happens: A key built from letter counts lists 26 numbers. Joined without a separator, different counts can give the same string. One a with eleven bs gives "111" followed by zeros. Eleven as with one b gives the same string.

    Example: Do "a" + "b" * 11 and "a" * 11 + "b" get the same key?
    Mistake: returns TrueFix: returns False
Not this one if…
  • The question counts contiguous subarrays with an exact sum, as in Subarray Sum Equals K (LC 560): the keys you look up are running sums of a prefix, not the items themselves. See Prefix Sum Map

Deep Dive

A hash map answers one question: where is this key?

A hash map is a table that finds the entry for a key in one step, on average. It does not scan the other entries. This is the only property the card uses.

The card's work is to choose the key. The key is whatever the question asks about. Two items that should be treated as the same must produce the same key. Two items that should be kept apart must produce different keys.

In group_anagrams the key is "".join(sorted(s)). Two words are anagrams exactly when they have the same letters with the same counts, and sorting the letters puts those letters in one fixed order. So anagrams get one key and all other words get another.

In two_sum the key is a number. index_of[num] is an index where that number was seen. For a new num, the question "which earlier number completes the pair?" becomes one lookup of partner = target - num.

Both functions read the input once. The map holds what was read so far, and each new item asks the map one question.

Group Anagrams, traced on six words

Take strs = ["aab", "eat", "abb", "tea", "baa", "ate"]. groups starts empty.

"aab" has the key "aab". The key is new, so group 1 starts with ["aab"]. "eat" has the key "aet", so group 2 starts. "abb" has the key "abb", which is different from "aab", so group 3 starts.

"tea" also has the key "aet", so it joins group 2. "baa" has the key "aab" and joins group 1. "ate" has the key "aet" and joins group 2.

The board also marks the trap after "aab". A set key would give "aab" and "abb" the same letters a and b, and the two words would share a group.

The result is [["aab", "baa"], ["eat", "tea", "ate"], ["abb"]]. No step compared two words with each other. Each word cost one sort and one map operation.

On the LeetCode example ["eat", "tea", "tan", "ate", "nat", "bat"], the same code returns [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]].

The key must keep exactly the facts that matter

A key can fail in two ways. If it throws away a fact that matters, different items collide. If it keeps a fact that does not matter, equal items are split.

frozenset("aab") keeps the set of letters, {a, b}, and drops the counts. "abb" gives the same set, so the two words share a group although they are not anagrams. On ["aab", "abb"], the right answer is [["aab"], ["abb"]], and the set key returns [["aab", "abb"]].

sorted(s) keeps everything, but it returns a list. A list can change, so Python does not allow it as a dictionary key and raises a TypeError. Use "".join(sorted(s)) or tuple(sorted(s)).

A key made of 26 letter counts avoids sorting. Use tuple(count). If you join the counts into a string, add a separator, because "1" + "11" and "11" + "1" become the same text "111".

Cost, and the order of lookup and store in Two Sum

For group_anagrams, let N be the number of words and K the longest word length. Each word costs O(K log K) to sort, so the time is O(N * K log K). The map stores every word once, so the space is O(N * K). A count key replaces the sort with an O(K) pass, and the time becomes O(N * K).

For two_sum, each of the N numbers costs one lookup and one store. The time is O(N) and the space is O(N). The comparison is a double loop over all pairs, which costs O(N^2).

In two_sum, the lookup must come before the store. If index_of[num] = i runs first, partner in index_of can find num itself when partner == num. For nums = [3, 2, 4] and target = 6, the store-first code returns [0, 0] instead of [1, 2].

A repeated number is not a problem with the right order. For nums = [3, 3] and target = 6, the first 3 is stored at index 0. The second 3 looks up partner = 3 and finds index 0, so the result is [0, 1].

60-Second Pitch

"Comparing every pair of words costs O(N^2) comparisons, so I choose a key instead. Two words are anagrams when their sorted letters are equal, so "".join(sorted(s)) is the key, and I keep a dictionary from each key to its words. One pass reads each word, computes its key and appends it to groups[key]. No two words are compared. The time is O(N * K log K) for N words of length at most K, and the space is O(N * K). The key must keep the letter counts. A set of letters would merge "aab" and "abb". For Two Sum the same idea stores each number's index, and I look up the partner before I store the current number."

Common Questions

Why is a hash map lookup O(1)?

The map turns a key into a position with a hash function and goes straight to that position. Different keys can share a position, but a good hash function keeps those collisions rare. So a lookup takes constant time on average. The worst case is O(N) when many keys collide.

Why do you sort the letters to make the key?

Two words are anagrams when they have the same letters with the same counts. Sorting puts the letters in one fixed order, so every anagram of a word gives the same text, and any other word gives different text.

How can Group Anagrams run without the sort?

Count the letters of each word in a list of 26 numbers and use tuple(count) as the key. The pass over a word costs O(K) instead of O(K log K), so the total time is O(N * K). The key is a tuple because a list cannot be a dictionary key.

What changes in Two Sum if the numbers are sorted?

Use two pointers, one at each end of the sorted list. They need O(1) extra space and the same O(N) time. The hash map costs O(N) space, but it also works on unsorted input, which is what Two Sum (LC 1) gives you.

How do you count items instead of grouping them?

Store a number as the value instead of a list. counts[key] += 1 for each item gives the frequency of every key in one pass. Valid Anagram (LC 242) compares two such count tables.

SWE Reasoning

Spot it

  • The question asks "have I seen this before?", "how many of each?" or "which items belong together?" (Group Anagrams (LC 49), Contains Duplicate (LC 217)).
  • A partner that completes a target can be named from the current item, as in Two Sum (LC 1).
  • Items that belong together can be given an equal key, computed from the item alone.
  • A double loop over all pairs is the obvious answer, and it is too slow.

The invariant, step by step

  1. groups starts empty. It maps a key to every word read so far that has that key.
  2. For each word s, compute key = "".join(sorted(s)). The key depends only on the letters and their counts.
  3. groups[key].append(s) adds the word to the right group. A new key creates a new group.
  4. After each word, groups[key] holds exactly the words so far whose sorted letters equal key.
  5. After the last word, list(groups.values()) is the answer. No two words were compared with each other.

Traps

  • A key that drops information. frozenset(s) puts "aab" and "abb" in one group, because it forgets the letter counts.
  • A key that cannot be stored. sorted(s) is a list, and a list raises a TypeError as a dictionary key.
  • Store before lookup in two_sum. index_of[num] = i before the lookup lets a number pair with itself. The call two_sum([3, 2, 4], 6) returns [0, 0].
  • Count keys joined with no separator. One a with eleven b letters and eleven a letters with one b give the same text.

Practice

Canonical LeetCode:Group Anagrams (LC 49)Two Sum (LC 1)Valid Anagram (LC 242)Contains Duplicate (LC 217)Valid Sudoku (LC 36)Ransom Note (LC 383)Isomorphic Strings (LC 205)Detect Squares (LC 2013)Binary Tree Vertical Order Traversal (LC 314)Line Reflection (LC 356)Perfect Rectangle (LC 391)Check If N and Its Double Exist (LC 1346)Random Pick Index (LC 398)Minimum Index of a Valid Split (LC 2780)Copy List with Random Pointer (LC 138)Contains Duplicate II (LC 219)
Theory Context•Two Pointers & Sliding Window
P0EasyMental model~2 min speedrun

Hashing & Frequency Maps (Group Anagrams, Two Sum)

Grows into
Hash-Set Sequence Scanning
Unlocks
Fixed WindowTrieMost Frequent FirstLRU CachePrefix Sum MapInsert Delete GetRandomCanonical KeyBucket SortKMPConstruct Binary Tree
The problem

Group Anagrams (LC 49)(opens leetcode.com)

You get a list of words strs, all written in lowercase letters. Two words are anagrams of each other when you can reorder the letters of one to spell the other, so they use exactly the same letters, each the same number of times: "eat" and "tea" are anagrams, "aab" and "abb" are not.

Split strs into groups so that each group holds every word that is an anagram of the others in it, and return the list of groups. A word that has no anagram in the list forms a group on its own, and a word that appears twice is listed twice. The groups can come in any order, and so can the words inside each group.

Example 1
Input:strs = ["eat","tea","tan","ate","nat","bat"]
Output:[["bat"],["nat","tan"],["ate","eat","tea"]]
"ate", "eat" and "tea" all sort to "aet"; "nat" and "tan" both sort to "ant"; nothing else sorts to "abt", so "bat" stands alone.
strs
eat0tea1tan2ate3nat4bat5
key "aet"
eat0tea1ate2
key "ant"
tan0nat1
key "abt"
bat0

Hash Map

items ➔ computed key ➔ dict lookup or count in O(1)
Spot it when: The question is "have I seen this before?", "how many of each?" or "which items belong together?": duplicates, counts, a partner that completes a target, or items grouped by a key you compute from them.
Hash Map
Python
1from collections import defaultdict
2 
3 
4def group_anagrams(strs: list[str]) -> list[list[str]]:
5 """Group Anagrams: words with the same sorted letters share one key in a hash map.
6 Time: O(N * K log K) | Space: O(N * K)
7 """
8 groups: dict[str, list[str]] = defaultdict(list) # key -> every word read so far with that key
9 # 1. File Each Word Under Its Key:
10 # Words with the same letters and the same counts produce the same sorted-letters key ("eat", "tea" -> "aet"), so every anagram lands in the same group without comparing any two words.
11 for s in strs:
12 key = "".join(sorted(s))
13 groups[key].append(s)
14 return list(groups.values())
15 
16 
17def two_sum(nums: list[int], target: int) -> list[int]:
18 """Two Sum: remember each value's index, and look up the partner each new value needs.
19 Time: O(N) | Space: O(N)
20 """
21 index_of: dict[int, int] = {} # value -> an index where it was seen
22 # 1. Look Up The Partner Before Storing:
23 # If target - num was already seen, its stored index completes the pair; storing num only after the lookup keeps num from ever pairing with itself.
24 for i, num in enumerate(nums):
25 partner = target - num
26 if partner in index_of:
27 return [index_of[partner], i]
28 index_of[num] = i
29 return [] # LeetCode guarantees exactly one answer, so this line is never reached
Invariant: After each item, the map holds every item read so far under its exact key: groups[key] is precisely the words so far whose sorted letters equal key.

A hash map finds a key's entry in one step, so pick the key the question is about and store under it what the answer needs: in group_anagrams the key is a word's sorted letters and the value is every word read so far with that key; in two_sum the key is a number and the value is its index. Items that belong together produce equal keys, so groups[key].append(s) files every anagram in one pass without comparing any two words, and partner in index_of finds an earlier number that completes the pair without a second loop. The trap is a lossy key: it must be equal only for items that truly belong together, so key on "".join(sorted(s)), which keeps repeated letters, never on frozenset(s) or set(s), which drop them and put "aab" and "abb" in one group.

See it run

Count and Lookup, step by step

key - · groups 0

key aab · groups 0

key aab · groups 1

key aab · groups 1

key aet · groups 1

key aet · groups 2

key abb · groups 2

key abb · groups 3

key aet · groups 3

key aet · groups 3

key aab · groups 3

key aab · groups 3

key aet · groups 3

key aet · groups 3

key - · groups 3 · answer aab,baa | eat,tea,ate | abb

The top row is strs. The middle row is the key of each word. The bottom row is the number of the group that holds the word.

  • read the word being read from strs
  • key the sorted letters of that word
  • filed the group number the word was added to

Start: groups is empty and strs has 6 words. Each word will get a key and a group number.

key = "".join(sorted("aab")) = "aab".

groups["aab"] did not exist, so it starts as a new group 1 holding "aab".

Trap: frozenset("aab") and frozenset("abb") are both the letters "ab". A set key would put these two words in one group, but they are not anagrams.

key = "".join(sorted("eat")) = "aet".

groups["aet"] did not exist, so it starts as a new group 2 holding "eat".

key = "".join(sorted("abb")) = "abb".

groups["abb"] did not exist, so it starts as a new group 3 holding "abb".

key = "".join(sorted("tea")) = "aet".

groups["aet"] is group 2, so "tea" joins it. No other word was compared.

key = "".join(sorted("baa")) = "aab".

groups["aab"] is group 1, so "baa" joins it. No other word was compared.

key = "".join(sorted("ate")) = "aet".

groups["aet"] is group 2, so "ate" joins it. No other word was compared.

Done: 3 groups, aab,baa | eat,tea,ate | abb.

strs
0aab
1eat
2abb
3tea
4baa
5ate
key
group
1 / 15
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Keying on frozenset(s) instead of the sorted letters

    Mistake
    key = frozenset(s)
    Fix
    key = "".join(sorted(s))

    What happens: A set keeps each letter once and drops the repeats. "aab" and "abb" both give the set of a and b. They land in one group, but they are not anagrams. "".join(sorted(s)) keeps every letter, repeats included.

    Example: strs = ["aab", "abb"]
    Mistake: returns [['aab', 'abb']]Fix: returns [['aab'], ['abb']]
  2. 2

    Gotcha 2: Using the list from sorted(s) as the key

    Mistake
    key = sorted(s)
    Fix
    key = "".join(sorted(s))

    What happens: sorted(s) returns a list. A list can change, so Python does not allow it as a dict key. groups[key] raises a TypeError. Join the letters into a string, or wrap the list in tuple(...).

    Example: strs = ["eat", "tea"]
    Mistake: raises TypeErrorFix: returns [['eat', 'tea']]
  3. 3

    Gotcha 3: Storing num before looking up its partner

    Mistake
    partner = target - num
    index_of[num] = i
    if partner in index_of:
        return [index_of[partner], i]
    Fix
    partner = target - num
    if partner in index_of:
        return [index_of[partner], i]
    index_of[num] = i

    What happens: index_of[num] = i makes num visible to the lookup. If it runs first, partner in index_of can find num itself when partner == num. Then two_sum pairs one element with itself.

    Example: nums = [3, 2, 4], target = 6
    Mistake: returns [0, 0]Fix: returns [1, 2]
  4. 4

    Gotcha 4: Joining letter counts into a key without a separator

    Mistake
    key = "".join(map(str, count))
    Fix
    key = tuple(count)

    What happens: A key built from letter counts lists 26 numbers. Joined without a separator, different counts can give the same string. One a with eleven bs gives "111" followed by zeros. Eleven as with one b gives the same string.

    Example: Do "a" + "b" * 11 and "a" * 11 + "b" get the same key?
    Mistake: returns TrueFix: returns False
Not this one if…
  • The question counts contiguous subarrays with an exact sum, as in Subarray Sum Equals K (LC 560): the keys you look up are running sums of a prefix, not the items themselves. See Prefix Sum Map

Deep Dive

A hash map answers one question: where is this key?

A hash map is a table that finds the entry for a key in one step, on average. It does not scan the other entries. This is the only property the card uses.

The card's work is to choose the key. The key is whatever the question asks about. Two items that should be treated as the same must produce the same key. Two items that should be kept apart must produce different keys.

In group_anagrams the key is "".join(sorted(s)). Two words are anagrams exactly when they have the same letters with the same counts, and sorting the letters puts those letters in one fixed order. So anagrams get one key and all other words get another.

In two_sum the key is a number. index_of[num] is an index where that number was seen. For a new num, the question "which earlier number completes the pair?" becomes one lookup of partner = target - num.

Both functions read the input once. The map holds what was read so far, and each new item asks the map one question.

Group Anagrams, traced on six words

Take strs = ["aab", "eat", "abb", "tea", "baa", "ate"]. groups starts empty.

"aab" has the key "aab". The key is new, so group 1 starts with ["aab"]. "eat" has the key "aet", so group 2 starts. "abb" has the key "abb", which is different from "aab", so group 3 starts.

"tea" also has the key "aet", so it joins group 2. "baa" has the key "aab" and joins group 1. "ate" has the key "aet" and joins group 2.

The board also marks the trap after "aab". A set key would give "aab" and "abb" the same letters a and b, and the two words would share a group.

The result is [["aab", "baa"], ["eat", "tea", "ate"], ["abb"]]. No step compared two words with each other. Each word cost one sort and one map operation.

On the LeetCode example ["eat", "tea", "tan", "ate", "nat", "bat"], the same code returns [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]].

The key must keep exactly the facts that matter

A key can fail in two ways. If it throws away a fact that matters, different items collide. If it keeps a fact that does not matter, equal items are split.

frozenset("aab") keeps the set of letters, {a, b}, and drops the counts. "abb" gives the same set, so the two words share a group although they are not anagrams. On ["aab", "abb"], the right answer is [["aab"], ["abb"]], and the set key returns [["aab", "abb"]].

sorted(s) keeps everything, but it returns a list. A list can change, so Python does not allow it as a dictionary key and raises a TypeError. Use "".join(sorted(s)) or tuple(sorted(s)).

A key made of 26 letter counts avoids sorting. Use tuple(count). If you join the counts into a string, add a separator, because "1" + "11" and "11" + "1" become the same text "111".

Cost, and the order of lookup and store in Two Sum

For group_anagrams, let N be the number of words and K the longest word length. Each word costs O(K log K) to sort, so the time is O(N * K log K). The map stores every word once, so the space is O(N * K). A count key replaces the sort with an O(K) pass, and the time becomes O(N * K).

For two_sum, each of the N numbers costs one lookup and one store. The time is O(N) and the space is O(N). The comparison is a double loop over all pairs, which costs O(N^2).

In two_sum, the lookup must come before the store. If index_of[num] = i runs first, partner in index_of can find num itself when partner == num. For nums = [3, 2, 4] and target = 6, the store-first code returns [0, 0] instead of [1, 2].

A repeated number is not a problem with the right order. For nums = [3, 3] and target = 6, the first 3 is stored at index 0. The second 3 looks up partner = 3 and finds index 0, so the result is [0, 1].

60-Second Pitch

"Comparing every pair of words costs O(N^2) comparisons, so I choose a key instead. Two words are anagrams when their sorted letters are equal, so "".join(sorted(s)) is the key, and I keep a dictionary from each key to its words. One pass reads each word, computes its key and appends it to groups[key]. No two words are compared. The time is O(N * K log K) for N words of length at most K, and the space is O(N * K). The key must keep the letter counts. A set of letters would merge "aab" and "abb". For Two Sum the same idea stores each number's index, and I look up the partner before I store the current number."

Common Questions

Why is a hash map lookup O(1)?

The map turns a key into a position with a hash function and goes straight to that position. Different keys can share a position, but a good hash function keeps those collisions rare. So a lookup takes constant time on average. The worst case is O(N) when many keys collide.

Why do you sort the letters to make the key?

Two words are anagrams when they have the same letters with the same counts. Sorting puts the letters in one fixed order, so every anagram of a word gives the same text, and any other word gives different text.

How can Group Anagrams run without the sort?

Count the letters of each word in a list of 26 numbers and use tuple(count) as the key. The pass over a word costs O(K) instead of O(K log K), so the total time is O(N * K). The key is a tuple because a list cannot be a dictionary key.

What changes in Two Sum if the numbers are sorted?

Use two pointers, one at each end of the sorted list. They need O(1) extra space and the same O(N) time. The hash map costs O(N) space, but it also works on unsorted input, which is what Two Sum (LC 1) gives you.

How do you count items instead of grouping them?

Store a number as the value instead of a list. counts[key] += 1 for each item gives the frequency of every key in one pass. Valid Anagram (LC 242) compares two such count tables.

SWE Reasoning

Spot it

  • The question asks "have I seen this before?", "how many of each?" or "which items belong together?" (Group Anagrams (LC 49), Contains Duplicate (LC 217)).
  • A partner that completes a target can be named from the current item, as in Two Sum (LC 1).
  • Items that belong together can be given an equal key, computed from the item alone.
  • A double loop over all pairs is the obvious answer, and it is too slow.

The invariant, step by step

  1. groups starts empty. It maps a key to every word read so far that has that key.
  2. For each word s, compute key = "".join(sorted(s)). The key depends only on the letters and their counts.
  3. groups[key].append(s) adds the word to the right group. A new key creates a new group.
  4. After each word, groups[key] holds exactly the words so far whose sorted letters equal key.
  5. After the last word, list(groups.values()) is the answer. No two words were compared with each other.

Traps

  • A key that drops information. frozenset(s) puts "aab" and "abb" in one group, because it forgets the letter counts.
  • A key that cannot be stored. sorted(s) is a list, and a list raises a TypeError as a dictionary key.
  • Store before lookup in two_sum. index_of[num] = i before the lookup lets a number pair with itself. The call two_sum([3, 2, 4], 6) returns [0, 0].
  • Count keys joined with no separator. One a with eleven b letters and eleven a letters with one b give the same text.

Practice

Canonical LeetCode:Group Anagrams (LC 49)Two Sum (LC 1)Valid Anagram (LC 242)Contains Duplicate (LC 217)Valid Sudoku (LC 36)Ransom Note (LC 383)Isomorphic Strings (LC 205)Detect Squares (LC 2013)Binary Tree Vertical Order Traversal (LC 314)Line Reflection (LC 356)Perfect Rectangle (LC 391)Check If N and Its Double Exist (LC 1346)Random Pick Index (LC 398)Minimum Index of a Valid Split (LC 2780)Copy List with Random Pointer (LC 138)Contains Duplicate II (LC 219)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•