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•Breadth-First Search (BFS)
MediumLC 752

Open the Lock (LeetCode 752)

Target Frequency:GoogleMeta

A lock has 4 wheels side by side, and each wheel shows one digit from '0' to '9'. One move turns a single wheel by one position, up or down, and the wheels wrap around: turning up from '9' shows '0', and turning down from '0' shows '9'. The lock begins at "0000", and what it displays is always a 4-character string.

deadends lists codes that jam the lock: as soon as the display shows one of them, the wheels can't be turned any more and the lock can't be opened. Return the smallest number of moves that makes the display show target without ever showing a code from deadends, or -1 when no sequence of moves can do it.

Worked Examples

Example 1
Input:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output:6
Explanation: The two neighbours of "0202" that are only 3 turns from the start, "0102" and "0201", are both dead ends, so the lock has to take a detour. One 6-turn route: "0000" -> "0010" -> "0110" -> "0210" -> "0211" -> "0212" -> "0202".
Example 2
Input:deadends = ["8888"], target = "0009"
Output:1
Explanation: Turning the last wheel down once wraps it from 0 to 9, so a single turn is enough.
Example 3
Input:deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output:-1
Explanation: All eight codes one turn away from "8888" are dead ends, so every route to it would have to pass through a jammed code: it can't be reached.

⚖️Formal Constraints & Bounds

  • 1 <= deadends.length <= 500

  • deadends[i].length == 4

  • target.length == 4

  • target will not be in the list deadends.

  • target and deadends[i] consist of digits only.

Deep-Dive & Conceptual Insights

Core Insight

Each code is a state with 8 neighbours built on the spot; the dead ends go into seen before the search, every other state is marked when it is pushed, and the first pop of target gives the fewest turns.

Real-World Scenario & Production Applications

Combination-lock / PIN-recovery minimum-turn puzzles and finite-state device configuration search where each 'wheel' is an independent setting with wraparound.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Dead Ends Into seen, Then Check the Start

A dead end may never be entered, so put every one into seen before the search. If "0000" is itself a dead end, no move is possible: return -1.

Mathematical Recurrence / Code Invariant
seen = set(deadends)
if "0000" in seen:
    return -1

Step-by-Step Execution Trace Table

Example 1, deadends = ["0201","0101","0102","1212","2002"], target = "0202". Neighbours are built wheel by wheel, up before down.

Pop #state (steps)Neighbours builtSkipped: already in seenPushed with steps + 1
1"0000" (0)1000, 9000, 0100, 0900, 0010, 0090, 0001, 0009noneall 8
2"1000" (1)2000, 0000, 1100, 1900, 1010, 1090, 1001, 10090000 (popped)7, including 1100
3"9000" (1)0000, 8000, 9100, 9900, 9010, 9090, 9001, 900900007
4"0100" (1)1100, 9100, 0200, 0000, 0110, 0190, 0101, 01091100 and 9100 (already queued: marked when pushed), 0000, 0101 (dead end)0200, 0110, 0190, 0109
...levels 1 to 5......8, 31, 86, 190 and 356 codes are popped at 1, 2, 3, 4 and 5 turns
822"0202" (6)not neededstate == target: return 6
Trace Inputdeadends = ["0201","0101","0102","1212","2002"], target = "0202"
Expected6

Example 1, deadends = ["0201","0101","0102","1212","2002"], target = "0202". Neighbours are built wheel by wheel, up before down.

Pop #state (steps)Neighbours builtSkipped: already in seenPushed with steps + 1
1"0000" (0)1000, 9000, 0100, 0900, 0010, 0090, 0001, 0009noneall 8
2"1000" (1)2000, 0000, 1100, 1900, 1010, 1090, 1001, 10090000 (popped)7, including 1100
3"9000" (1)0000, 8000, 9100, 9900, 9010, 9090, 9001, 900900007
4"0100" (1)1100, 9100, 0200, 0000, 0110, 0190, 0101, 01091100 and 9100 (already queued: marked when pushed), 0000, 0101 (dead end)0200, 0110, 0190, 0109
...levels 1 to 5......8, 31, 86, 190 and 356 codes are popped at 1, 2, 3, 4 and 5 turns
822"0202" (6)not neededstate == target: return 6
Code Shape
Archetype Code Shape
Python
1seen = set(deadends)
2if '0000' in seen:
3 return -1
4seen.add('0000')
5queue = deque([('0000', 0)])
6while queue:
7 state, steps = queue.popleft()
8 if state == target:
9 return steps
10 for i in range(4):
11 for move in (1, -1):
12 neighbor = state with wheel i turned to (digit + move) % 10
13 if neighbor not in seen:
14 seen.add(neighbor)
15 queue.append((neighbor, steps + 1))
16return -1

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Open the Lock is shortest-path BFS on a graph that is never written down. Each 4-digit code the lock can show is a state, and one turn of one wheel is an edge of cost 1. There are 10,000 codes and each has exactly 8 neighbours (4 wheels, each up or down), but nobody stores those edges: openLock builds the neighbours of a state only when that state comes off the queue.

🏟️ The Analogy: A Maze of 10,000 Rooms

Picture a maze with one room per code, 0000 to 9999. You start in room 0000, and every room has 8 doors: turn one of the 4 wheels up or down. From 0000 the doors lead to 1000 and 9000, 0100 and 0900, 0010 and 0090, 0001 and 0009. The dead ends are rooms full of lava: you may never step in, so they go into seen before the search starts, as if already visited. BFS walks the maze ring by ring: first every room one turn away, then every room two turns away, until it opens the target's door.

🔑 The Key Idea in the Code

(digit + move) % 10 is the wheel: Python's % always returns a value from 0 to 9 here, so (0 - 1) % 10 is 9 and (9 + 1) % 10 is 0, with no if for the ends. And seen.add(neighbor) sits next to queue.append, not after popleft: "1100" is one turn from both "1000" and "0100", and marking it only when it is popped would push it twice. With 8 neighbours per state, the queue fills with copies of the same codes.

  • Keying seen on part of the state

  • Not checking whether "0000" is a dead end

  • Building the next digit without % 10

  • Searching from one side when the goal is known

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks for the smallest number of moves, and one move turns a single wheel by one position: fewest moves when every move costs the same means BFS. But there is no grid and no edge list. Where the lock is, is the whole 4-digit code, and the codes one move away have to be built from it. That is State-Space BFS: BFS over states you generate yourself, with the jamming codes as states you may never enter.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

The decision rule (the card's invariant keeps it correct): push a neighbor only when neighbor not in seen, calling seen.add(neighbor) in the same step, and return steps the first time state == target after popleft.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • A partial key: seen.add(neighbor) stores the whole code, all four wheels. Keyed on the first three wheels only, "0001" and "0009" look like the already-seen "0000" and are never pushed, so Example 2 (target = "0009") returns -1 instead of 1.

  • Marking on pop: seen.add(neighbor) must run together with queue.append, not after popleft. "1100" is one turn from both "1000" and "0100", and marking on pop pushes it once for each of them.

  • Testing the target at the wrong moment: return steps when target is popped, or steps + 1 when a pushed neighbor is the target; mixing the two is off by one. The pop-time test also covers target == "0000", whose answer is 0.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use State-Space BFS. Every code is a state and one turn is a move of cost one: a shortest path on a graph of ten thousand codes I never build. I put the dead ends into seen first, and return minus one at once if zero-zero-zero-zero is dead. Then I pop a state, return its steps if it is the target, and otherwise build its eight neighbours: each wheel up or down, mod ten so it wraps. I push a neighbour only if it is not in seen, and mark it in that same step. The trap is keying seen on less than the whole state: two codes that share three wheels are still different states, just as a maze cell visited with more keys is a new state. BFS pops in order of steps, so the first pop of the target is the answer. With W wheels and D dead ends: O(10 to the W, times W squared, plus D times W) time, and O(10 to the W, times W, plus D times W) space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(10^W * W^2 + D * W)

Look at the code: seen = set(deadends) hashes D strings of W digits, O(D * W). seen.add(neighbor) runs together with queue.append, so each of the at most 10^W codes is pushed once and popped once. Each pop runs for i in range(len(state)) (W wheels) times for move in (1, -1) (2 moves): 2W neighbours, and each neighbor = state[:i] + str((digit + move) % 10) + state[i + 1:] builds and hashes a W-character string in O(W). So one pop costs O(W^2), and all pops together O(10^W * W^2).

SPACE COMPLEXITY

O(10^W * W + D * W)

Look at what is alive at once: seen holds the D dead ends plus every pushed code, at most 10^W of them, each a W-character string; queue holds at most 10^W (state, steps) pairs. There is no recursion, and the answer is one integer.

Formal Recurrence Relation

T = D * W (dead ends into seen) + 10^W pops x 2W neighbours x O(W) per neighbour = O(10^W * W^2 + D * W)

Derivation Progression

Dead ends into seen

set(deadends): D strings x O(W)

Hashing each dead end reads its W digits.

States

at most 10^W codes, each pushed and popped once

seen.add(neighbor) runs together with queue.append, so no code is pushed twice.

Neighbours per pop

for i in range(len(state)) x for move in (1, -1): 2W

Each wheel is turned one slot up and one slot down.

Work per neighbour

state[:i] + str((digit + move) % 10) + state[i + 1:] and the seen lookup: O(W)

Building and hashing a W-character string.

Total

D * W + 10^W x 2W x O(W) = O(10^W * W^2 + D * W)

With W = 4 that is at most 10,000 pops and 80,000 neighbours built.

Variable Definitions

WWW

len(state), the number of wheels (4 here, so 10^W = 10,000 codes)

DDD

len(deadends), the number of dead ends (1 to 500)

Memory Architecture & Bounds

🟣 Call Stack

O(1): no recursion

🔵 Auxiliary Heap

O(10^W * W + D * W): seen (dead ends plus pushed codes) and queue

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(D∗W)O(D * W)O(D∗W): "0000" is a dead end, or target is "0000"

Average Case

O(10W∗W2+D∗W)O(10^W * W^2 + D * W)O(10W∗W2+D∗W)

Worst Case

O(10W∗W2+D∗W)O(10^W * W^2 + D * W)O(10W∗W2+D∗W): target can't be reached, so every reachable code is popped (Example 3 pops 9,991 codes)

Graph & Tree Traversal Frontier

Graph & Tree Traversal Frontier
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "smallest number of moves", "one move turns a single wheel by one position", "codes that jam the lock". Fewest moves at equal cost, but the position is a whole 4-digit code, not a cell of a grid: State-Space BFS over states generated on the spot, with the dead ends as states you may never enter.

CONSTRAINTS & BOUNDS

W=4W = 4W=4 wheels, so at most 104=10,00010^4 = 10{,}000104=10,000 codes with 2W=82W = 82W=8 neighbours each, and up to 500500500 dead ends: the whole space fits in memory. At scale it stops fitting: with W=8W = 8W=8 there are 10810^8108 codes, and a set of 10810^8108 strings is too big for one process. Encode each code as an integer (a 10810^8108-bit bitmap is 12.5 MB) or search from both ends at once.

FAANG PRODUCTION TRAPS & EDGE CASES

Queue memory is the first thing to run out at scale: every duplicate push is a queue slot, which is why marking on push matters more here than the time it saves. When the space is too large even with compact states, search from both ends (bidirectional BFS) so each side only goes about half the depth.

Core Algorithmic State Invariants

  1. steps Travels With Each State

Pushing (neighbor, steps + 1) gives every popped code its own distance, so no level-by-level loop is needed. Counting rounds with level_size, as in Level-Order BFS, gives the same answer; the tuple just keeps the loop flat.

Theory Context•Breadth-First Search (BFS)
MediumLC 752

Open the Lock (LeetCode 752)

Target Frequency:GoogleMeta

A lock has 4 wheels side by side, and each wheel shows one digit from '0' to '9'. One move turns a single wheel by one position, up or down, and the wheels wrap around: turning up from '9' shows '0', and turning down from '0' shows '9'. The lock begins at "0000", and what it displays is always a 4-character string.

deadends lists codes that jam the lock: as soon as the display shows one of them, the wheels can't be turned any more and the lock can't be opened. Return the smallest number of moves that makes the display show target without ever showing a code from deadends, or -1 when no sequence of moves can do it.

Worked Examples

Example 1
Input:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output:6
Explanation: The two neighbours of "0202" that are only 3 turns from the start, "0102" and "0201", are both dead ends, so the lock has to take a detour. One 6-turn route: "0000" -> "0010" -> "0110" -> "0210" -> "0211" -> "0212" -> "0202".
Example 2
Input:deadends = ["8888"], target = "0009"
Output:1
Explanation: Turning the last wheel down once wraps it from 0 to 9, so a single turn is enough.
Example 3
Input:deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output:-1
Explanation: All eight codes one turn away from "8888" are dead ends, so every route to it would have to pass through a jammed code: it can't be reached.

⚖️Formal Constraints & Bounds

  • 1 <= deadends.length <= 500

  • deadends[i].length == 4

  • target.length == 4

  • target will not be in the list deadends.

  • target and deadends[i] consist of digits only.

Deep-Dive & Conceptual Insights

Core Insight

Each code is a state with 8 neighbours built on the spot; the dead ends go into seen before the search, every other state is marked when it is pushed, and the first pop of target gives the fewest turns.

Real-World Scenario & Production Applications

Combination-lock / PIN-recovery minimum-turn puzzles and finite-state device configuration search where each 'wheel' is an independent setting with wraparound.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Dead Ends Into seen, Then Check the Start

A dead end may never be entered, so put every one into seen before the search. If "0000" is itself a dead end, no move is possible: return -1.

Mathematical Recurrence / Code Invariant
seen = set(deadends)
if "0000" in seen:
    return -1

Step-by-Step Execution Trace Table

Example 1, deadends = ["0201","0101","0102","1212","2002"], target = "0202". Neighbours are built wheel by wheel, up before down.

Pop #state (steps)Neighbours builtSkipped: already in seenPushed with steps + 1
1"0000" (0)1000, 9000, 0100, 0900, 0010, 0090, 0001, 0009noneall 8
2"1000" (1)2000, 0000, 1100, 1900, 1010, 1090, 1001, 10090000 (popped)7, including 1100
3"9000" (1)0000, 8000, 9100, 9900, 9010, 9090, 9001, 900900007
4"0100" (1)1100, 9100, 0200, 0000, 0110, 0190, 0101, 01091100 and 9100 (already queued: marked when pushed), 0000, 0101 (dead end)0200, 0110, 0190, 0109
...levels 1 to 5......8, 31, 86, 190 and 356 codes are popped at 1, 2, 3, 4 and 5 turns
822"0202" (6)not neededstate == target: return 6
Trace Inputdeadends = ["0201","0101","0102","1212","2002"], target = "0202"
Expected6

Example 1, deadends = ["0201","0101","0102","1212","2002"], target = "0202". Neighbours are built wheel by wheel, up before down.

Pop #state (steps)Neighbours builtSkipped: already in seenPushed with steps + 1
1"0000" (0)1000, 9000, 0100, 0900, 0010, 0090, 0001, 0009noneall 8
2"1000" (1)2000, 0000, 1100, 1900, 1010, 1090, 1001, 10090000 (popped)7, including 1100
3"9000" (1)0000, 8000, 9100, 9900, 9010, 9090, 9001, 900900007
4"0100" (1)1100, 9100, 0200, 0000, 0110, 0190, 0101, 01091100 and 9100 (already queued: marked when pushed), 0000, 0101 (dead end)0200, 0110, 0190, 0109
...levels 1 to 5......8, 31, 86, 190 and 356 codes are popped at 1, 2, 3, 4 and 5 turns
822"0202" (6)not neededstate == target: return 6
Code Shape
Archetype Code Shape
Python
1seen = set(deadends)
2if '0000' in seen:
3 return -1
4seen.add('0000')
5queue = deque([('0000', 0)])
6while queue:
7 state, steps = queue.popleft()
8 if state == target:
9 return steps
10 for i in range(4):
11 for move in (1, -1):
12 neighbor = state with wheel i turned to (digit + move) % 10
13 if neighbor not in seen:
14 seen.add(neighbor)
15 queue.append((neighbor, steps + 1))
16return -1

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Open the Lock is shortest-path BFS on a graph that is never written down. Each 4-digit code the lock can show is a state, and one turn of one wheel is an edge of cost 1. There are 10,000 codes and each has exactly 8 neighbours (4 wheels, each up or down), but nobody stores those edges: openLock builds the neighbours of a state only when that state comes off the queue.

🏟️ The Analogy: A Maze of 10,000 Rooms

Picture a maze with one room per code, 0000 to 9999. You start in room 0000, and every room has 8 doors: turn one of the 4 wheels up or down. From 0000 the doors lead to 1000 and 9000, 0100 and 0900, 0010 and 0090, 0001 and 0009. The dead ends are rooms full of lava: you may never step in, so they go into seen before the search starts, as if already visited. BFS walks the maze ring by ring: first every room one turn away, then every room two turns away, until it opens the target's door.

🔑 The Key Idea in the Code

(digit + move) % 10 is the wheel: Python's % always returns a value from 0 to 9 here, so (0 - 1) % 10 is 9 and (9 + 1) % 10 is 0, with no if for the ends. And seen.add(neighbor) sits next to queue.append, not after popleft: "1100" is one turn from both "1000" and "0100", and marking it only when it is popped would push it twice. With 8 neighbours per state, the queue fills with copies of the same codes.

  • Keying seen on part of the state

  • Not checking whether "0000" is a dead end

  • Building the next digit without % 10

  • Searching from one side when the goal is known

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks for the smallest number of moves, and one move turns a single wheel by one position: fewest moves when every move costs the same means BFS. But there is no grid and no edge list. Where the lock is, is the whole 4-digit code, and the codes one move away have to be built from it. That is State-Space BFS: BFS over states you generate yourself, with the jamming codes as states you may never enter.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

The decision rule (the card's invariant keeps it correct): push a neighbor only when neighbor not in seen, calling seen.add(neighbor) in the same step, and return steps the first time state == target after popleft.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • A partial key: seen.add(neighbor) stores the whole code, all four wheels. Keyed on the first three wheels only, "0001" and "0009" look like the already-seen "0000" and are never pushed, so Example 2 (target = "0009") returns -1 instead of 1.

  • Marking on pop: seen.add(neighbor) must run together with queue.append, not after popleft. "1100" is one turn from both "1000" and "0100", and marking on pop pushes it once for each of them.

  • Testing the target at the wrong moment: return steps when target is popped, or steps + 1 when a pushed neighbor is the target; mixing the two is off by one. The pop-time test also covers target == "0000", whose answer is 0.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use State-Space BFS. Every code is a state and one turn is a move of cost one: a shortest path on a graph of ten thousand codes I never build. I put the dead ends into seen first, and return minus one at once if zero-zero-zero-zero is dead. Then I pop a state, return its steps if it is the target, and otherwise build its eight neighbours: each wheel up or down, mod ten so it wraps. I push a neighbour only if it is not in seen, and mark it in that same step. The trap is keying seen on less than the whole state: two codes that share three wheels are still different states, just as a maze cell visited with more keys is a new state. BFS pops in order of steps, so the first pop of the target is the answer. With W wheels and D dead ends: O(10 to the W, times W squared, plus D times W) time, and O(10 to the W, times W, plus D times W) space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(10^W * W^2 + D * W)

Look at the code: seen = set(deadends) hashes D strings of W digits, O(D * W). seen.add(neighbor) runs together with queue.append, so each of the at most 10^W codes is pushed once and popped once. Each pop runs for i in range(len(state)) (W wheels) times for move in (1, -1) (2 moves): 2W neighbours, and each neighbor = state[:i] + str((digit + move) % 10) + state[i + 1:] builds and hashes a W-character string in O(W). So one pop costs O(W^2), and all pops together O(10^W * W^2).

SPACE COMPLEXITY

O(10^W * W + D * W)

Look at what is alive at once: seen holds the D dead ends plus every pushed code, at most 10^W of them, each a W-character string; queue holds at most 10^W (state, steps) pairs. There is no recursion, and the answer is one integer.

Formal Recurrence Relation

T = D * W (dead ends into seen) + 10^W pops x 2W neighbours x O(W) per neighbour = O(10^W * W^2 + D * W)

Derivation Progression

Dead ends into seen

set(deadends): D strings x O(W)

Hashing each dead end reads its W digits.

States

at most 10^W codes, each pushed and popped once

seen.add(neighbor) runs together with queue.append, so no code is pushed twice.

Neighbours per pop

for i in range(len(state)) x for move in (1, -1): 2W

Each wheel is turned one slot up and one slot down.

Work per neighbour

state[:i] + str((digit + move) % 10) + state[i + 1:] and the seen lookup: O(W)

Building and hashing a W-character string.

Total

D * W + 10^W x 2W x O(W) = O(10^W * W^2 + D * W)

With W = 4 that is at most 10,000 pops and 80,000 neighbours built.

Variable Definitions

WWW

len(state), the number of wheels (4 here, so 10^W = 10,000 codes)

DDD

len(deadends), the number of dead ends (1 to 500)

Memory Architecture & Bounds

🟣 Call Stack

O(1): no recursion

🔵 Auxiliary Heap

O(10^W * W + D * W): seen (dead ends plus pushed codes) and queue

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(D∗W)O(D * W)O(D∗W): "0000" is a dead end, or target is "0000"

Average Case

O(10W∗W2+D∗W)O(10^W * W^2 + D * W)O(10W∗W2+D∗W)

Worst Case

O(10W∗W2+D∗W)O(10^W * W^2 + D * W)O(10W∗W2+D∗W): target can't be reached, so every reachable code is popped (Example 3 pops 9,991 codes)

Graph & Tree Traversal Frontier

Graph & Tree Traversal Frontier
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "smallest number of moves", "one move turns a single wheel by one position", "codes that jam the lock". Fewest moves at equal cost, but the position is a whole 4-digit code, not a cell of a grid: State-Space BFS over states generated on the spot, with the dead ends as states you may never enter.

CONSTRAINTS & BOUNDS

W=4W = 4W=4 wheels, so at most 104=10,00010^4 = 10{,}000104=10,000 codes with 2W=82W = 82W=8 neighbours each, and up to 500500500 dead ends: the whole space fits in memory. At scale it stops fitting: with W=8W = 8W=8 there are 10810^8108 codes, and a set of 10810^8108 strings is too big for one process. Encode each code as an integer (a 10810^8108-bit bitmap is 12.5 MB) or search from both ends at once.

FAANG PRODUCTION TRAPS & EDGE CASES

Queue memory is the first thing to run out at scale: every duplicate push is a queue slot, which is why marking on push matters more here than the time it saves. When the space is too large even with compact states, search from both ends (bidirectional BFS) so each side only goes about half the depth.

Core Algorithmic State Invariants

  1. steps Travels With Each State

Pushing (neighbor, steps + 1) gives every popped code its own distance, so no level-by-level loop is needed. Counting rounds with level_size, as in Level-Order BFS, gives the same answer; the tuple just keeps the loop flat.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•