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).

12 Core Algorithmic Patterns & 189 Practice Problems

  • 1. Two Pointers (10 Paradigms, 34 Problems): Converging pointers, sorted pair sums, container with most water, trapping rain water, 3Sum, plus the Sliding Window and Fast & Slow Pointers paradigms (Floyd cycle detection, monotonic window invariants, longest substrings, minimum window).
  • 2. Binary Search (8 Paradigms, 12 Problems): Monotonic predicate partitioning, boundary searching, rotated arrays, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 8 Problems): Bitmasking, XOR tricks, counting set bits, subset enumeration via bitmasks.
  • 4. Math & Geometry (7 Paradigms, 13 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (10 Paradigms, 19 Problems): Path sums, lowest common ancestor, tree diameter, subtree serialization, validating BSTs.
  • 6. Tree/Graph Breadth-First Search (4 Paradigms, 11 Problems): Level-order traversals, shortest path, rotting oranges, word ladders.
  • 7. Graphs (10 Paradigms, 18 Problems): Topological sort, cycle detection, Dijkstra, bipartite validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens.
  • 9. Dynamic Programming (11 Paradigms, 18 Problems): Memoization vs tabulation, knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (8 Paradigms, 10 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (6 Paradigms, 14 Problems): Trie, Union-Find, LRU Cache, LFU Cache, Monotonic Stacks.
  • 12. Intervals & Stack / Miscellaneous (11 Paradigms, 18 Problems): Merge intervals, daily temperatures, largest rectangle in histogram, trapping rain water via stack.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Multi-language Rosetta Stone contrasting abstract invariants with concrete solutions across Python, C#, Java, TypeScript, C++, Go, and Rust.
  2. Stage 2 (Active Recall): Reconstruct algorithmic template invariants from memory with real-time feedback before looking at solutions.
  3. Stage 3 (Senior SWE AI Mock Coach): Simulated senior mock interview evaluating Big-O space/time tradeoffs, edge cases, and code reviews in Monaco Editor.
  4. Stage 4 (Solve on Your Own): Timed sandbox challenges verified against automated test suites in Python, C#, Java, and TypeScript.

Equipped with SM-2 Spaced Repetition Review Hub, Studio Cockpit workspace layout, and interactive study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Compute Coins: 40 free coins upon signup, +20 daily login bonus, +25 referral bonus.
  • 24-Hour AI Coaching Pass: 5 compute coins unlocks unlimited senior SWE AI coaching for a full 24 hours.
  • BYOK (Bring Your Own Key): Completely free unlimited AI coaching if using your own Gemini/OpenAI API key.
  • Refund & Subscription Policy: No recurring charges, no subscription traps, and no paid paywalls. Free tier provides full learning path.
  • Platform Operator: Hi👋WebEnterprise Inc. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

201Items
Theory Context•Graph Algorithms
Medium

Planets and Kingdoms

You will see how two depth-first passes over a graph and its transpose group every mutually-reachable planet into one kingdom.

Target Frequency:GoogleAmazon

A game world has n planets and m one-way teleporters. Planets a and b belong to the same kingdom exactly when there is a route from a to b and a route back from b to a, each route following any number of teleporters in the direction they point.

The planets are numbered 1 to n. Each teleporter is a pair [a, b]: it lets you travel from planet a to planet b, never the other way, unless a separate teleporter says so. Work out how many kingdoms the planets split into, and which kingdom every planet belongs to. Kingdoms may be labeled any way you like, from 1 to the number of kingdoms, as long as two planets share a label exactly when they share a kingdom.

Worked Examples

Example 1
Input:n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]]
Output:2 kingdoms: planets 1, 2, 3 in kingdom 1; planets 4, 5 in kingdom 2
123450
Explanation: Planets 1, 2 and 3 form a cycle, so each reaches the other two and back: one kingdom. Planets 4 and 5 form their own cycle, a second kingdom. A teleporter runs from 3 into 4, but nothing runs back from kingdom 2 into kingdom 1, so the two groups stay separate.
Example 2
Input:n = 3, teleporters = [[1,2],[2,3]]
Output:3 kingdoms: planet 1 alone, planet 2 alone, planet 3 alone
1230
Explanation: Planet 1 reaches 2 and 3, but nothing reaches back to planet 1: no cycle at all, so every planet is its own kingdom.

⚖️Formal Constraints & Bounds

  • 1 <= n <= 105

  • 1 <= m <= 2 * 105 (the number of teleporters)

  • 1 <= a, b <= n

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

A planet finishes the first pass only once everything it can reach has already finished, so processing finish order from last to first always starts the second pass at a planet with no teleporter, in the original graph, into any kingdom not yet found; walking the transpose from there then reaches exactly the planets that can also reach it back.

Real-World Scenario & Production Applications

Finding mutually-dependent clusters in any directed relationship: modules whose imports form cycles in a build graph, accounts that can all wire funds to each other in a payments graph, or web pages that can each be reached from every other by following links.

Step-by-Step Execution Trace Table

Step-by-Step Kosaraju's Algorithm (n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]])
  1. Step 1 (First Pass, on adj): DFS from planet 1: 1 -> 2 -> 3 -> 1 (already visited, skip) -> 4 -> 5 -> 4 (already visited): planet 5 has no unvisited neighbor left, so it finishes first, order = [5]; then 4 finishes: order = [5, 4]; then 3: order = [5, 4, 3]; then 2: order = [5, 4, 3, 2]; then 1: order = [5, 4, 3, 2, 1].
  2. Step 2 (Second Pass Starts at the Last Finisher): reversed(order) = [1, 2, 3, 4, 5]. Start at planet 1, walk radj: radj[1] = [3] (from teleporter 3 -> 1), radj[3] = [2], radj[2] = [1] (already labeled). Kingdom 1 = {1, 2, 3}.
  3. Step 3 (Next Unlabeled Finisher): 2 and 3 are already labeled; 4 is not. Start at planet 4, walk radj: radj[4] = [3, 5] (3 already labeled, 5 not), so kingdom grows to include 5; radj[5] = [4] (already labeled). Kingdom 2 = {4, 5}.
  4. Step 4 (Done): Every planet has a kingdom: 1, 2, 3 -> kingdom 1; 4, 5 -> kingdom 2 ✅.
Full Walkthrough4 Steps
Input:n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]]Expected:2 kingdoms: planets 1, 2, 3 in kingdom 1; planets 4, 5 in kingdom 2
1⚡ STEP(First Pass, on `adj`)
DFS from planet 1: 1 -> 2 -> 3 -> 1 (already visited, skip) -> 4 -> 5 -> 4 (already visited): planet 5 has no unvisited neighbor left, so it finishes first, order = [5]; then 4 finishes: order = [5, 4]; then 3: order = [5, 4, 3]; then 2: order = [5, 4, 3, 2]; then 1: order = [5, 4, 3, 2, 1].
2⚡ STEP(Second Pass Starts at the Last Finisher)
reversed(order) = [1, 2, 3, 4, 5]. Start at planet 1, walk radj: radj[1] = [3] (from teleporter 3 -> 1), radj[3] = [2], radj[2] = [1] (already labeled). Kingdom 1 = {1, 2, 3}.
3⚡ STEP(Next Unlabeled Finisher)
2 and 3 are already labeled; 4 is not. Start at planet 4, walk radj: radj[4] = [3, 5] (3 already labeled, 5 not), so kingdom grows to include 5; radj[5] = [4] (already labeled). Kingdom 2 = {4, 5}.
4✅ RECORD / GOAL(Done)
Every planet has a kingdom: 1, 2, 3 -> kingdom 1; 4, 5 -> kingdom 2 ✅.
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1adj, radj = build the graph and its transpose
2for start in 1..n: if unvisited, iterative DFS on adj, append(node) only when it has no unvisited neighbor left
3for node in reversed(order): # decreasing finish time
4 if already labeled: continue
5 new label; DFS from node on radj, labeling everything reached
6return the labels

Target: Planets and Kingdoms. Both directions are kept: adj drives the first pass, radj drives the second

Boundary Model: Graph Component Traversal & Visited State Machine

Maintains visited set or 3-color states (WHITE=unvisited, GREY=visiting/cycle, BLACK=processed) across adjacency lists.

Loop Invariant Termination

Loop outer vertices 0..V-1 to handle disconnected subgraphs; explore edges via BFS/DFS/PriorityQueue.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Kosaraju's algorithm finds every strongly connected component of a directed graph with two depth-first passes. The first pass walks the graph as given and records each node's finish order: the order in which a DFS runs out of unvisited neighbors to descend into. The second pass walks the transpose graph -- every edge reversed -- starting from unvisited nodes in decreasing finish order. Each tree the second pass grows is exactly one strongly connected component, because a node that finished late in the first pass has nothing left unexplored beneath it, so it is a "source" of whatever graph structure remains, and walking the transpose from a source reaches precisely the nodes that can also reach it back in the original graph.

🌌 The Analogy: Draining a Reservoir System From the Lowest Outlet

Picture a network of reservoirs connected by one-way spillways. The first pass is a survey: starting anywhere, follow spillways downstream and note, for each reservoir, the moment you have nothing left downstream of it to explore -- reservoirs that are true dead ends (or whose whole downstream network is already surveyed) get noted first. The second pass works backward: starting from the LAST reservoir noted (the one truly furthest downstream, still unassigned), follow the spillways upstream instead. Every reservoir reached this way can flow downstream back to the start (that is how you reached it walking upstream), and the start can flow to all of them too, since they were reachable walking downstream in the first pass restricted to this same neighborhood -- so they are one kingdom, drained together. Moving to the next unassigned reservoir (again picking the latest-noted one) always starts a fresh kingdom, because every reservoir it could drain into upstream is already claimed.

🪄 Breaking Down the Code's "Magic Trick"
Code / Blueprint
for planet in reversed(order): # decreasing finish time: always a source of what's left
if kingdom[planet] != -1:
continue
kingdom_id += 1
kingdom[planet] = kingdom_id
stack = [planet]
while stack:
u = stack.pop()
for v in radj[u]: # the TRANSPOSE graph, not adj again
if kingdom[v] == -1:
kingdom[v] = kingdom_id
stack.append(v)
 

Two things have to both be true for this to work. First, reversed(order) -- processing late finishers first -- guarantees each new component's starting planet has no edge, in the original graph, into any component that hasn't been found yet; if a lower-finishing planet were picked instead, its component could still have an edge into a not-yet-discovered one, and the walk would wrongly reach across that boundary. Second, radj: walking the transpose from that starting planet reaches every planet that can reach IT in the original graph. Combined with the fact those planets are also reachable FROM it (they finished no later, in the same downstream neighborhood surveyed in pass one), the set found is exactly the group that can reach each other both ways -- a kingdom.

💡 Summary

Two O(V + E) depth-first passes -- one to order nodes by how "finished" they are, one on the reversed graph in that order -- peel every strongly connected component off a directed graph in linear time, with no extra structure beyond the two adjacency lists and a visited/component array.

  • Checking only one direction: a node can reach everyone but not be reached by everyone (or the reverse); strong connectivity needs the SCC count to be exactly 1, not just "one DFS from node 1 reaches all n nodes".

  • Assuming any two components give a witness pair: an arbitrary pair of nodes from two different components is not always unreachable in one specific direction; the component with the topological order's last id cannot reach the component with the first id (never the other way round), so the witness must be built from that ordering, not any two mismatched nodes.

  • Off-by-one on kingdom labels: cities are 1-indexed and CSES-style outputs commonly start labels at 1, not 0; comp arrays built 0-indexed still need [1:] slicing or a +1 shift before they are printed.

  • Recomputing SCCs per query pair: for large n, do not re-run Kosaraju's algorithm once per candidate pair; compute the component labels once, in one O(V + E) pass, then read off the answer.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer spots a two-way-reachability grouping question and defends Kosaraju's two passes.

Pattern Recognition Signals

The 10-second spot

Two planets belong together exactly when "there is a route both from a to b and from b to a": mutual reachability, on a directed graph, is a strongly connected component question, not a plain reachability or shortest-path one. "Determine for each planet its kingdom" asks for every group at once, up to 105 planets and 2 * 105 teleporters, so the answer has to come from one linear pass, not a search per pair.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

comp[u] == comp[v] should hold exactly when u and v can reach each other. Kosaraju's invariant is on the finish order: once the first DFS pass is done, a node's position in order reflects that everything reachable from it is already finished, so starting the second pass from the node finishing last, and walking the transpose graph from there, reaches exactly the nodes that can reach it back.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The second pass walks radj, the transpose, not adj again: for v in radj[u]. Reusing adj folds every node merely reachable FROM a component into it, even when the trip back doesn't exist.

  • The second pass must start from unvisited nodes in reversed(order) -- decreasing finish time. Starting from the front of order (increasing finish time, or no particular order) can start a component at a node that still has an unfound component ahead of it in the condensation, and the transpose walk then crosses into it.

  • DFS is run iteratively, with an explicit stack, never recursively: at n up to 105 planets, one long teleporter chain overflows Python's default recursion limit.

  • A node is appended to order only once it has no unvisited neighbor left, not the moment it is first visited: that is finish order, and the whole argument for why the second pass can't cross an unfound boundary depends on it being finish order specifically.

The 60-Second Interview Pitch

Say this out loud before you type a single line

This is a mutual-reachability grouping question on a directed graph, so I'd use Kosaraju's algorithm: two depth-first passes. The first pass walks the graph as given and records each node's finish order -- the order in which its DFS runs out of unvisited neighbors below it. The second pass walks the transpose graph, every edge reversed, starting new trees at unvisited nodes taken in decreasing finish order. Each tree the second pass grows is exactly one kingdom. The trap is picking the wrong graph or the wrong order for the second pass: it has to be the transpose, and it has to start from the latest unfinished node, or the walk can cross into a kingdom that hasn't been found yet. Both passes are iterative DFS, since a recursive one can overflow the call stack on a long chain of planets. It's O(V + E) time and O(V + E) space.

So: first pass on adj records finish order, second pass on radj from unvisited nodes in decreasing finish order labels one kingdom per tree.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(V + E)

Look at the code: building adj and radj touches each of the E teleporters twice, O(V + E). The first pass's for start loop runs V times, but the inner while stack work, across the whole pass, visits each node once (visited guards re-entry) and scans each node's out-edges once total across all its stack frames, so the whole pass is O(V + E). The second pass is the same shape on radj: O(V + E). Building comp and slicing it is O(V). Total: O(V + E).

SPACE COMPLEXITY

O(V + E)

adj and radj together hold 2E entries: O(E). visited, order, comp are each O(V). The explicit stack in either pass holds at most one frame per node on the current DFS path, O(V). Nothing is recursive, so there is no hidden call-stack cost beyond that.

Formal Recurrence Relation

T(V, E) = (V + E) [build adj, radj] + (V + E) [first pass] + (V + E) [second pass] = O(V + E)

Look at the code: building adj and radj touches each of the E teleporters twice, O(V + E). The first pass's for start loop runs V times, but the inner while stack work, across the whole pass, visits each node once (visited guards re-entry) and scans each node's out-edges once total across all its stack frames, so the whole pass is O(V + E). The second pass is the same shape on radj: O(V + E). Building comp and slicing it is O(V). Total: O(V + E).

Derivation Progression

Build the graph and its transpose

2E appends: O(V + E)

Every teleporter is added once to adj and once to radj.

First pass: finish order

V starts, each node visited once, each out-edge scanned once: O(V + E)

The visited guard means every node's stack frame is pushed exactly once, and every edge is looked at exactly once across the whole pass.

Second pass: label kingdoms

V starts (in reversed(order)), each node visited once, each transpose edge scanned once: O(V + E)

Same shape as the first pass, run on radj instead of adj.

Variable Definitions

VVV

Number of planets, n

EEE

Number of teleporters, len(teleporters)

Memory Architecture & Bounds

🟣 Call Stack

O(1): both passes use an explicit stack, no recursion

🔵 Auxiliary Heap

O(V + E): adj and radj hold 2E entries; visited, order and comp hold O(V) each

🟢 Output Space

O(V): the V kingdom labels of comp[1:]

Boundary Best / Worst Cases

Best Case

O(V+E)O(V + E)O(V+E): every planet and teleporter is touched once regardless of the graph's shape.

Average Case

O(V+E)O(V + E)O(V+E).

Worst Case

O(V+E)O(V + E)O(V+E).

Graph & Tree Traversal Frontier

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

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

High-frequency interview keyword triggers that immediately dictate this algorithmic archetype.

CONSTRAINTS & BOUNDS

Input boundaries and maximum time complexity budgets.

FAANG PRODUCTION TRAPS & EDGE CASES

Subtle off-by-one errors and edge conditions.

Core Algorithmic State Invariants

1. Component Membership Invariant

Every unvisited cell initiates a new distinct connected component. Traversal (DFS/BFS) marks all reachable symmetric neighbors visited, preventing duplicate component counts.

2. Immediate Visited Mark Invariant

Cells are mutated in-place ('0') or recorded in a visited set immediately upon discovery, preventing exponential redundant enqueuing and recursion stack overflow.

3. Bounded Grid Traversal Guarantee

Each cell is visited a constant number of times (bounded by 4 * M * N operations), terminating cleanly when all grid components are exhausted.

Theory Context•Graph Algorithms
Medium

Planets and Kingdoms

You will see how two depth-first passes over a graph and its transpose group every mutually-reachable planet into one kingdom.

Target Frequency:GoogleAmazon

A game world has n planets and m one-way teleporters. Planets a and b belong to the same kingdom exactly when there is a route from a to b and a route back from b to a, each route following any number of teleporters in the direction they point.

The planets are numbered 1 to n. Each teleporter is a pair [a, b]: it lets you travel from planet a to planet b, never the other way, unless a separate teleporter says so. Work out how many kingdoms the planets split into, and which kingdom every planet belongs to. Kingdoms may be labeled any way you like, from 1 to the number of kingdoms, as long as two planets share a label exactly when they share a kingdom.

Worked Examples

Example 1
Input:n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]]
Output:2 kingdoms: planets 1, 2, 3 in kingdom 1; planets 4, 5 in kingdom 2
123450
Explanation: Planets 1, 2 and 3 form a cycle, so each reaches the other two and back: one kingdom. Planets 4 and 5 form their own cycle, a second kingdom. A teleporter runs from 3 into 4, but nothing runs back from kingdom 2 into kingdom 1, so the two groups stay separate.
Example 2
Input:n = 3, teleporters = [[1,2],[2,3]]
Output:3 kingdoms: planet 1 alone, planet 2 alone, planet 3 alone
1230
Explanation: Planet 1 reaches 2 and 3, but nothing reaches back to planet 1: no cycle at all, so every planet is its own kingdom.

⚖️Formal Constraints & Bounds

  • 1 <= n <= 105

  • 1 <= m <= 2 * 105 (the number of teleporters)

  • 1 <= a, b <= n

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

A planet finishes the first pass only once everything it can reach has already finished, so processing finish order from last to first always starts the second pass at a planet with no teleporter, in the original graph, into any kingdom not yet found; walking the transpose from there then reaches exactly the planets that can also reach it back.

Real-World Scenario & Production Applications

Finding mutually-dependent clusters in any directed relationship: modules whose imports form cycles in a build graph, accounts that can all wire funds to each other in a payments graph, or web pages that can each be reached from every other by following links.

Step-by-Step Execution Trace Table

Step-by-Step Kosaraju's Algorithm (n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]])
  1. Step 1 (First Pass, on adj): DFS from planet 1: 1 -> 2 -> 3 -> 1 (already visited, skip) -> 4 -> 5 -> 4 (already visited): planet 5 has no unvisited neighbor left, so it finishes first, order = [5]; then 4 finishes: order = [5, 4]; then 3: order = [5, 4, 3]; then 2: order = [5, 4, 3, 2]; then 1: order = [5, 4, 3, 2, 1].
  2. Step 2 (Second Pass Starts at the Last Finisher): reversed(order) = [1, 2, 3, 4, 5]. Start at planet 1, walk radj: radj[1] = [3] (from teleporter 3 -> 1), radj[3] = [2], radj[2] = [1] (already labeled). Kingdom 1 = {1, 2, 3}.
  3. Step 3 (Next Unlabeled Finisher): 2 and 3 are already labeled; 4 is not. Start at planet 4, walk radj: radj[4] = [3, 5] (3 already labeled, 5 not), so kingdom grows to include 5; radj[5] = [4] (already labeled). Kingdom 2 = {4, 5}.
  4. Step 4 (Done): Every planet has a kingdom: 1, 2, 3 -> kingdom 1; 4, 5 -> kingdom 2 ✅.
Full Walkthrough4 Steps
Input:n = 5, teleporters = [[1,2],[2,3],[3,1],[3,4],[4,5],[5,4]]Expected:2 kingdoms: planets 1, 2, 3 in kingdom 1; planets 4, 5 in kingdom 2
1⚡ STEP(First Pass, on `adj`)
DFS from planet 1: 1 -> 2 -> 3 -> 1 (already visited, skip) -> 4 -> 5 -> 4 (already visited): planet 5 has no unvisited neighbor left, so it finishes first, order = [5]; then 4 finishes: order = [5, 4]; then 3: order = [5, 4, 3]; then 2: order = [5, 4, 3, 2]; then 1: order = [5, 4, 3, 2, 1].
2⚡ STEP(Second Pass Starts at the Last Finisher)
reversed(order) = [1, 2, 3, 4, 5]. Start at planet 1, walk radj: radj[1] = [3] (from teleporter 3 -> 1), radj[3] = [2], radj[2] = [1] (already labeled). Kingdom 1 = {1, 2, 3}.
3⚡ STEP(Next Unlabeled Finisher)
2 and 3 are already labeled; 4 is not. Start at planet 4, walk radj: radj[4] = [3, 5] (3 already labeled, 5 not), so kingdom grows to include 5; radj[5] = [4] (already labeled). Kingdom 2 = {4, 5}.
4✅ RECORD / GOAL(Done)
Every planet has a kingdom: 1, 2, 3 -> kingdom 1; 4, 5 -> kingdom 2 ✅.
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1adj, radj = build the graph and its transpose
2for start in 1..n: if unvisited, iterative DFS on adj, append(node) only when it has no unvisited neighbor left
3for node in reversed(order): # decreasing finish time
4 if already labeled: continue
5 new label; DFS from node on radj, labeling everything reached
6return the labels

Target: Planets and Kingdoms. Both directions are kept: adj drives the first pass, radj drives the second

Boundary Model: Graph Component Traversal & Visited State Machine

Maintains visited set or 3-color states (WHITE=unvisited, GREY=visiting/cycle, BLACK=processed) across adjacency lists.

Loop Invariant Termination

Loop outer vertices 0..V-1 to handle disconnected subgraphs; explore edges via BFS/DFS/PriorityQueue.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Kosaraju's algorithm finds every strongly connected component of a directed graph with two depth-first passes. The first pass walks the graph as given and records each node's finish order: the order in which a DFS runs out of unvisited neighbors to descend into. The second pass walks the transpose graph -- every edge reversed -- starting from unvisited nodes in decreasing finish order. Each tree the second pass grows is exactly one strongly connected component, because a node that finished late in the first pass has nothing left unexplored beneath it, so it is a "source" of whatever graph structure remains, and walking the transpose from a source reaches precisely the nodes that can also reach it back in the original graph.

🌌 The Analogy: Draining a Reservoir System From the Lowest Outlet

Picture a network of reservoirs connected by one-way spillways. The first pass is a survey: starting anywhere, follow spillways downstream and note, for each reservoir, the moment you have nothing left downstream of it to explore -- reservoirs that are true dead ends (or whose whole downstream network is already surveyed) get noted first. The second pass works backward: starting from the LAST reservoir noted (the one truly furthest downstream, still unassigned), follow the spillways upstream instead. Every reservoir reached this way can flow downstream back to the start (that is how you reached it walking upstream), and the start can flow to all of them too, since they were reachable walking downstream in the first pass restricted to this same neighborhood -- so they are one kingdom, drained together. Moving to the next unassigned reservoir (again picking the latest-noted one) always starts a fresh kingdom, because every reservoir it could drain into upstream is already claimed.

🪄 Breaking Down the Code's "Magic Trick"
Code / Blueprint
for planet in reversed(order): # decreasing finish time: always a source of what's left
if kingdom[planet] != -1:
continue
kingdom_id += 1
kingdom[planet] = kingdom_id
stack = [planet]
while stack:
u = stack.pop()
for v in radj[u]: # the TRANSPOSE graph, not adj again
if kingdom[v] == -1:
kingdom[v] = kingdom_id
stack.append(v)
 

Two things have to both be true for this to work. First, reversed(order) -- processing late finishers first -- guarantees each new component's starting planet has no edge, in the original graph, into any component that hasn't been found yet; if a lower-finishing planet were picked instead, its component could still have an edge into a not-yet-discovered one, and the walk would wrongly reach across that boundary. Second, radj: walking the transpose from that starting planet reaches every planet that can reach IT in the original graph. Combined with the fact those planets are also reachable FROM it (they finished no later, in the same downstream neighborhood surveyed in pass one), the set found is exactly the group that can reach each other both ways -- a kingdom.

💡 Summary

Two O(V + E) depth-first passes -- one to order nodes by how "finished" they are, one on the reversed graph in that order -- peel every strongly connected component off a directed graph in linear time, with no extra structure beyond the two adjacency lists and a visited/component array.

  • Checking only one direction: a node can reach everyone but not be reached by everyone (or the reverse); strong connectivity needs the SCC count to be exactly 1, not just "one DFS from node 1 reaches all n nodes".

  • Assuming any two components give a witness pair: an arbitrary pair of nodes from two different components is not always unreachable in one specific direction; the component with the topological order's last id cannot reach the component with the first id (never the other way round), so the witness must be built from that ordering, not any two mismatched nodes.

  • Off-by-one on kingdom labels: cities are 1-indexed and CSES-style outputs commonly start labels at 1, not 0; comp arrays built 0-indexed still need [1:] slicing or a +1 shift before they are printed.

  • Recomputing SCCs per query pair: for large n, do not re-run Kosaraju's algorithm once per candidate pair; compute the component labels once, in one O(V + E) pass, then read off the answer.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer spots a two-way-reachability grouping question and defends Kosaraju's two passes.

Pattern Recognition Signals

The 10-second spot

Two planets belong together exactly when "there is a route both from a to b and from b to a": mutual reachability, on a directed graph, is a strongly connected component question, not a plain reachability or shortest-path one. "Determine for each planet its kingdom" asks for every group at once, up to 105 planets and 2 * 105 teleporters, so the answer has to come from one linear pass, not a search per pair.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

comp[u] == comp[v] should hold exactly when u and v can reach each other. Kosaraju's invariant is on the finish order: once the first DFS pass is done, a node's position in order reflects that everything reachable from it is already finished, so starting the second pass from the node finishing last, and walking the transpose graph from there, reaches exactly the nodes that can reach it back.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The second pass walks radj, the transpose, not adj again: for v in radj[u]. Reusing adj folds every node merely reachable FROM a component into it, even when the trip back doesn't exist.

  • The second pass must start from unvisited nodes in reversed(order) -- decreasing finish time. Starting from the front of order (increasing finish time, or no particular order) can start a component at a node that still has an unfound component ahead of it in the condensation, and the transpose walk then crosses into it.

  • DFS is run iteratively, with an explicit stack, never recursively: at n up to 105 planets, one long teleporter chain overflows Python's default recursion limit.

  • A node is appended to order only once it has no unvisited neighbor left, not the moment it is first visited: that is finish order, and the whole argument for why the second pass can't cross an unfound boundary depends on it being finish order specifically.

The 60-Second Interview Pitch

Say this out loud before you type a single line

This is a mutual-reachability grouping question on a directed graph, so I'd use Kosaraju's algorithm: two depth-first passes. The first pass walks the graph as given and records each node's finish order -- the order in which its DFS runs out of unvisited neighbors below it. The second pass walks the transpose graph, every edge reversed, starting new trees at unvisited nodes taken in decreasing finish order. Each tree the second pass grows is exactly one kingdom. The trap is picking the wrong graph or the wrong order for the second pass: it has to be the transpose, and it has to start from the latest unfinished node, or the walk can cross into a kingdom that hasn't been found yet. Both passes are iterative DFS, since a recursive one can overflow the call stack on a long chain of planets. It's O(V + E) time and O(V + E) space.

So: first pass on adj records finish order, second pass on radj from unvisited nodes in decreasing finish order labels one kingdom per tree.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(V + E)

Look at the code: building adj and radj touches each of the E teleporters twice, O(V + E). The first pass's for start loop runs V times, but the inner while stack work, across the whole pass, visits each node once (visited guards re-entry) and scans each node's out-edges once total across all its stack frames, so the whole pass is O(V + E). The second pass is the same shape on radj: O(V + E). Building comp and slicing it is O(V). Total: O(V + E).

SPACE COMPLEXITY

O(V + E)

adj and radj together hold 2E entries: O(E). visited, order, comp are each O(V). The explicit stack in either pass holds at most one frame per node on the current DFS path, O(V). Nothing is recursive, so there is no hidden call-stack cost beyond that.

Formal Recurrence Relation

T(V, E) = (V + E) [build adj, radj] + (V + E) [first pass] + (V + E) [second pass] = O(V + E)

Look at the code: building adj and radj touches each of the E teleporters twice, O(V + E). The first pass's for start loop runs V times, but the inner while stack work, across the whole pass, visits each node once (visited guards re-entry) and scans each node's out-edges once total across all its stack frames, so the whole pass is O(V + E). The second pass is the same shape on radj: O(V + E). Building comp and slicing it is O(V). Total: O(V + E).

Derivation Progression

Build the graph and its transpose

2E appends: O(V + E)

Every teleporter is added once to adj and once to radj.

First pass: finish order

V starts, each node visited once, each out-edge scanned once: O(V + E)

The visited guard means every node's stack frame is pushed exactly once, and every edge is looked at exactly once across the whole pass.

Second pass: label kingdoms

V starts (in reversed(order)), each node visited once, each transpose edge scanned once: O(V + E)

Same shape as the first pass, run on radj instead of adj.

Variable Definitions

VVV

Number of planets, n

EEE

Number of teleporters, len(teleporters)

Memory Architecture & Bounds

🟣 Call Stack

O(1): both passes use an explicit stack, no recursion

🔵 Auxiliary Heap

O(V + E): adj and radj hold 2E entries; visited, order and comp hold O(V) each

🟢 Output Space

O(V): the V kingdom labels of comp[1:]

Boundary Best / Worst Cases

Best Case

O(V+E)O(V + E)O(V+E): every planet and teleporter is touched once regardless of the graph's shape.

Average Case

O(V+E)O(V + E)O(V+E).

Worst Case

O(V+E)O(V + E)O(V+E).

Graph & Tree Traversal Frontier

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

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

High-frequency interview keyword triggers that immediately dictate this algorithmic archetype.

CONSTRAINTS & BOUNDS

Input boundaries and maximum time complexity budgets.

FAANG PRODUCTION TRAPS & EDGE CASES

Subtle off-by-one errors and edge conditions.

Core Algorithmic State Invariants

1. Component Membership Invariant

Every unvisited cell initiates a new distinct connected component. Traversal (DFS/BFS) marks all reachable symmetric neighbors visited, preventing duplicate component counts.

2. Immediate Visited Mark Invariant

Cells are mutated in-place ('0') or recorded in a visited set immediately upon discovery, preventing exponential redundant enqueuing and recursion stack overflow.

3. Bounded Grid Traversal Guarantee

Each cell is visited a constant number of times (bounded by 4 * M * N operations), terminating cleanly when all grid components are exhausted.

Rosetta Dual-Monaco Comparison
Python 3
CANONICAL INVARIANT TEMPLATE
Loading...
CONCRETE: PLANETS AND KINGDOMS
T = O(V + E)S = O(V + E)
Loading...
Pattern Implementation Mapping TableCanonical Invariant ⟷ Concrete Code ⟷ Engineering Rationale
Canonical InvariantConcrete CodeEngineering Rationale
Build the graph and its transposeadj = [[] for _ in range(n + 1)] radj = [[] for _ in range(n + 1)] for a, b in teleporters: adj[a].append(b) radj[b].append(a)Both directions are kept: adj drives the first pass, radj drives the second
First pass: record finish order on the original graphstack = [(start, iter(adj[start]))] ... order.append(planet)A planet is only appended once every teleporter it can reach has already finished
Second pass: unvisited starts, decreasing finish orderfor planet in reversed(order):The trap line: this always starts the next component at a source of what is left of the condensation
Second pass walks the transpose graphfor v in radj[u]: if kingdom[v] == -1: kingdom[v] = kingdom_id stack.append(v)The other trap: radj, not adj, or the walk leaks past this kingdom's own members
Return one label per nodereturn kingdom[1:]Planets and Kingdoms accepts any numbering, so labels start at 1 and count up as each kingdom is found
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•