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•Graph Algorithms
MediumLC 261

Graph Valid Tree (LeetCode 261)

What a tree is (assume nothing): a tree is an undirected graph in which every node can reach every other node, and no cycle exists. A cycle is a path of edges that starts and ends at the same node without using an edge twice.

You get n, the number of nodes, numbered 0 to n - 1. You also get edges, a list of pairs. Each pair [a, b] is an undirected edge: it joins node a and node b, and it can be walked in both directions.

Return true when these edges make one tree. Return false otherwise.

Worked Examples

Example 1
Input:n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Output:true
01234
Explanation: Node 0 joins nodes 1, 2 and 3, and node 1 joins node 4. All five nodes are in one group and no edge closes a loop.
Example 2
Input:n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
Output:false
01234
Explanation: The edges [1,2], [2,3] and [1,3] form a loop through nodes 1, 2 and 3, so this graph is not a tree.

⚖️Formal Constraints & Bounds

  • 1 <= n <= 2000

  • 0 <= edges.length <= 5000

  • edges[i].length == 2

  • 0 <= ai, bi < n

  • ai != bi

  • There are no self-loops or repeated edges.

Deep-Dive & Conceptual Insights

Core Insight

Remember the node each step came from. The edge back to that node is the edge just used, so skip it. Any other edge to a seen node is a second route and closes a cycle. A tree also needs every node reached, so the answer ends with reached == n.

Real-World Scenario & Production Applications

A company joins its offices with cables. Every office must be reachable from every other office, and the cables must not form a loop. A loop is a wasted cable, and a missing connection is an office that cannot be reached. This check finds both problems.

Step-by-Step Execution Trace Table

Trace of Example 2: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]. The lists are adj[0] = [1], adj[1] = [0, 2, 3, 4], adj[2] = [1, 3], adj[3] = [2, 1] and adj[4] = [1]. The search starts with seen[0] = True, reached = 1 and stack = [(0, -1)].

StepPop (u, parent)vWhat happensreachedstack after
1(0, -1)11 is new: seen[1] = True, push (1, 0)2[(1, 0)]
2(1, 0)00 is the parent: skip2[]
3(1, 0)22 is new: push (2, 1)3[(2, 1)]
4(1, 0)33 is new: push (3, 1)4[(2, 1), (3, 1)]
5(1, 0)44 is new: push (4, 1)5[(2, 1), (3, 1), (4, 1)]
6(4, 1)11 is the parent: skip5[(2, 1), (3, 1)]
7(3, 1)22 is seen and is not the parent 1: cycle, return False5[(2, 1)]

Node 2 was reached from node 1 by the edge [1,2], and node 3 was reached from node 1 by the edge [1,3]. The edge [2,3] joins them a second time, so it closes the cycle 1, 2, 3. In Example 1 the search ends with reached = 5 and no cycle, so the answer is True.

Trace Inputn = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Expectedtrue

Trace of Example 2: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]. The lists are adj[0] = [1], adj[1] = [0, 2, 3, 4], adj[2] = [1, 3], adj[3] = [2, 1] and adj[4] = [1]. The search starts with seen[0] = True, reached = 1 and stack = [(0, -1)].

StepPop (u, parent)vWhat happensreachedstack after
1(0, -1)11 is new: seen[1] = True, push (1, 0)2[(1, 0)]
2(1, 0)00 is the parent: skip2[]
3(1, 0)22 is new: push (2, 1)3[(2, 1)]
4(1, 0)33 is new: push (3, 1)4[(2, 1), (3, 1)]
5(1, 0)44 is new: push (4, 1)5[(2, 1), (3, 1), (4, 1)]
6(4, 1)11 is the parent: skip5[(2, 1), (3, 1)]
7(3, 1)22 is seen and is not the parent 1: cycle, return False5[(2, 1)]

Node 2 was reached from node 1 by the edge [1,2], and node 3 was reached from node 1 by the edge [1,3]. The edge [2,3] joins them a second time, so it closes the cycle 1, 2, 3. In Example 1 the search ends with reached = 5 and no cycle, so the answer is True.

Code Shape
Archetype Code Shape
Python
1adj = [[] for _ in range(n)]
2for u, v in edges:
3 adj[u].append(v)
4 adj[v].append(u)
5seen = [False] * n
6seen[0] = True
7reached = 1
8stack = [(0, -1)]
9while stack:
10 u, parent = stack.pop()
11 for v in adj[u]:
12 if v == parent:
13 continue
14 if seen[v]:
15 return False
16 seen[v] = True
17 reached += 1
18 stack.append((v, u))
19return reached == n

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

A tree is an undirected graph in which every node can reach every other node, and no cycle exists. A cycle is a path of edges that starts and ends at the same node without using an edge twice.

validTree checks both parts with one search that starts at node 0. The search keeps a stack of pairs. Each pair holds a node and the node it was reached from, called its parent.

🔌 Example: Cables Between Offices

Think of the nodes as offices and the edges as cables. You walk from office to office along the cables and write down each office you reach. When you stand in office u and look at its cables, one cable leads back to the office you just came from. That cable is not a problem, because it is the way you came. If any other cable leads to an office that is already on your list, you have found a second way to reach that office, so the cables form a loop.

🔑 The Key Idea in the Code

if v == parent: continue skips the cable you came by. if seen[v]: return False handles every other cable to a seen node. Without the first line, the cable back to parent would look like a loop, and every graph that has an edge would fail. return reached == n is the second half of the check. A graph with no loop but with separate groups is not a tree.

  • Returning the edge the search meets first: the search finds the cycle at one edge, but the answer is the cycle edge that comes last in edges. For edges = [[1,2],[2,3],[3,4],[1,4],[1,5]], the search meets [2,3] first and the answer is [1,4].

  • Allocating adj with n slots: the nodes are numbered from 1 to n, so adj[n] does not exist and raises an IndexError. Use n + 1 slots and leave slot 0 empty.

  • Searching with recursion: a path of 1000 nodes plus one extra edge needs about 1000 nested calls. That reaches Python's default recursion limit of 1000 and raises a RecursionError. The explicit stack has no such limit.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks whether n nodes and a list of undirected edges make "a valid tree". A tree is a graph with no cycle in which every node is connected, so the question has two parts: no cycle, and one connected group. The cycle part is an undirected cycle check with a parent skip.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

seen[u] is True once the search has reached u, and each seen node was reached once, from its parent. Every edge scanned so far either reached a new node or led back to parent. Any other edge to a seen node returns False. At the end, reached counts the nodes the search from node 0 reached.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • if v == parent: continue: without it, the edge back to parent finds parent already in seen. validTree(2, [[0, 1]]) returns False for a graph that is a tree.

  • return reached == n: return True here accepts a forest. validTree(4, [[0, 1], [2, 3]]) returns True instead of False.

  • reached = 1 before the loop: node 0 counts as reached. With reached = 0, a tree ends one short, and Example 1 returns False.

  • n = 1, edges = []: node 0 alone is a tree. The loop does not run, and reached == n is 1 == 1, so the answer is True.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd solve this with a cycle check that remembers the parent. A tree is a graph with no cycle in which every node is connected, so I check both parts. I start one search at node 0 with a stack. Each stack entry holds a node and the node it came from. When I scan a neighbour, I first skip the node I came from, because that edge is the one I just used. If the neighbour is any other node that is already seen, there are two routes between those nodes, so there is a cycle, and I return false. Otherwise I mark the neighbour seen, count it and push it. When the stack is empty, I return whether the count of reached nodes equals n. If it is smaller, some nodes are in another group. Each node and each edge is handled a constant number of times, so time is order V plus E, and space is order V plus E for the adjacency list.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(V + E)

Look at the code. Building adj reads each of the E edges once and appends two entries, so it takes O(E). A node enters stack only when seen[v] is False, and then seen[v] becomes True. So each node is pushed at most once, and stack.pop() runs at most V times. Each pop scans adj[u] once. Over the whole run, the scans read every adjacency entry once, which is 2E entries with constant work each. Total: O(V + E).

SPACE COMPLEXITY

O(V + E)

adj holds V lists with 2E entries in total. seen holds V flags. stack holds at most V entries, because each node is pushed once. There is no recursion, so the call stack is O(1). The total is O(V + E).

Formal Recurrence Relation

T(V, E) = E (build adj) + V (pops) + 2E (adjacency entries) = O(V + E)

Derivation Progression

Build the lists

E edges, 2 appends each = O(E)

for u, v in edges reads each edge once and stores it at both ends.

Each node is pushed and popped once

seen[v] is set before the push: at most V pops

A node is pushed only when seen[v] is False, and the push sets it to True.

Each adjacency entry is read once

sum of len(adj[u]) over popped nodes = 2E

Every edge sits in two lists and each list is scanned once, with constant work per entry: the parent test, the seen test, the push.

Variable Definitions

VVV

Number of nodes, n

EEE

Number of edges, len(edges); the adjacency lists hold 2E entries in total

Memory Architecture & Bounds

🟣 Call Stack

O(1): an explicit stack, no recursion

🔵 Auxiliary Heap

O(V + E): adj has 2E entries, seen has V flags and stack never holds more than V entries

🟢 Output Space

O(1): one boolean

Boundary Best / Worst Cases

Best Case

O(V+E)O(V + E)O(V+E) for the build, but the search can stop early: the first extra edge to a seen node returns False at once.

Average Case

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

Worst Case

O(V+E)O(V + E)O(V+E): a tree, where every node is pushed and every edge is read before return reached == n.

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: "valid tree", "undirected edges". A tree is a graph with no cycle in which every node is connected, so one search from node 0 answers both parts. An edge to a seen node that is not the parent is a cycle, and the count of reached nodes shows whether the graph is connected.

CONSTRAINTS & BOUNDS

n≤2000n \le 2000n≤2000 and at most 500050005000 edges, so one search is O(V+E)O(V + E)O(V+E), about 700070007000 steps. A recursive search can be 200020002000 calls deep, which passes Python's default limit of 100010001000, so the code uses an explicit stack. There are no repeated edges and no self-loops, so each pair of nodes appears at most once.

FAANG PRODUCTION TRAPS & EDGE CASES

Dropping if v == parent: continue reports a cycle on every edge: validTree(2, [[0, 1]]) returns False. Ending with return True instead of return reached == n accepts a forest: validTree(4, [[0, 1], [2, 3]]) returns True. Starting reached at 0 undercounts by one, so Example 1 returns False.

Core Algorithmic State Invariants

  1. Example 2, One Skip And One Cycle

The search skips the edge from node 1 back to its parent 0, and it pushes nodes 2, 3 and 4 with parent 1. Popping node 3, its neighbour 2 is already seen and is not its parent 1. So the edge 3-2 closes a cycle, and the answer is false.

  1. Example 1, No Cycle

Every edge back to a parent is skipped, and no other edge reaches a seen node. The search ends with reached = 5. That equals n, so the answer is true.

  1. No Cycle Is Not Enough

In n = 4, edges = [[0,1],[2,3]], no edge closes a cycle. The search from node 0 reaches only nodes 0 and 1, so reached = 2 is not n = 4. The answer is false: the nodes form two separate groups.

Theory Context•Graph Algorithms
MediumLC 261

Graph Valid Tree (LeetCode 261)

What a tree is (assume nothing): a tree is an undirected graph in which every node can reach every other node, and no cycle exists. A cycle is a path of edges that starts and ends at the same node without using an edge twice.

You get n, the number of nodes, numbered 0 to n - 1. You also get edges, a list of pairs. Each pair [a, b] is an undirected edge: it joins node a and node b, and it can be walked in both directions.

Return true when these edges make one tree. Return false otherwise.

Worked Examples

Example 1
Input:n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Output:true
01234
Explanation: Node 0 joins nodes 1, 2 and 3, and node 1 joins node 4. All five nodes are in one group and no edge closes a loop.
Example 2
Input:n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
Output:false
01234
Explanation: The edges [1,2], [2,3] and [1,3] form a loop through nodes 1, 2 and 3, so this graph is not a tree.

⚖️Formal Constraints & Bounds

  • 1 <= n <= 2000

  • 0 <= edges.length <= 5000

  • edges[i].length == 2

  • 0 <= ai, bi < n

  • ai != bi

  • There are no self-loops or repeated edges.

Deep-Dive & Conceptual Insights

Core Insight

Remember the node each step came from. The edge back to that node is the edge just used, so skip it. Any other edge to a seen node is a second route and closes a cycle. A tree also needs every node reached, so the answer ends with reached == n.

Real-World Scenario & Production Applications

A company joins its offices with cables. Every office must be reachable from every other office, and the cables must not form a loop. A loop is a wasted cable, and a missing connection is an office that cannot be reached. This check finds both problems.

Step-by-Step Execution Trace Table

Trace of Example 2: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]. The lists are adj[0] = [1], adj[1] = [0, 2, 3, 4], adj[2] = [1, 3], adj[3] = [2, 1] and adj[4] = [1]. The search starts with seen[0] = True, reached = 1 and stack = [(0, -1)].

StepPop (u, parent)vWhat happensreachedstack after
1(0, -1)11 is new: seen[1] = True, push (1, 0)2[(1, 0)]
2(1, 0)00 is the parent: skip2[]
3(1, 0)22 is new: push (2, 1)3[(2, 1)]
4(1, 0)33 is new: push (3, 1)4[(2, 1), (3, 1)]
5(1, 0)44 is new: push (4, 1)5[(2, 1), (3, 1), (4, 1)]
6(4, 1)11 is the parent: skip5[(2, 1), (3, 1)]
7(3, 1)22 is seen and is not the parent 1: cycle, return False5[(2, 1)]

Node 2 was reached from node 1 by the edge [1,2], and node 3 was reached from node 1 by the edge [1,3]. The edge [2,3] joins them a second time, so it closes the cycle 1, 2, 3. In Example 1 the search ends with reached = 5 and no cycle, so the answer is True.

Trace Inputn = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Expectedtrue

Trace of Example 2: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]. The lists are adj[0] = [1], adj[1] = [0, 2, 3, 4], adj[2] = [1, 3], adj[3] = [2, 1] and adj[4] = [1]. The search starts with seen[0] = True, reached = 1 and stack = [(0, -1)].

StepPop (u, parent)vWhat happensreachedstack after
1(0, -1)11 is new: seen[1] = True, push (1, 0)2[(1, 0)]
2(1, 0)00 is the parent: skip2[]
3(1, 0)22 is new: push (2, 1)3[(2, 1)]
4(1, 0)33 is new: push (3, 1)4[(2, 1), (3, 1)]
5(1, 0)44 is new: push (4, 1)5[(2, 1), (3, 1), (4, 1)]
6(4, 1)11 is the parent: skip5[(2, 1), (3, 1)]
7(3, 1)22 is seen and is not the parent 1: cycle, return False5[(2, 1)]

Node 2 was reached from node 1 by the edge [1,2], and node 3 was reached from node 1 by the edge [1,3]. The edge [2,3] joins them a second time, so it closes the cycle 1, 2, 3. In Example 1 the search ends with reached = 5 and no cycle, so the answer is True.

Code Shape
Archetype Code Shape
Python
1adj = [[] for _ in range(n)]
2for u, v in edges:
3 adj[u].append(v)
4 adj[v].append(u)
5seen = [False] * n
6seen[0] = True
7reached = 1
8stack = [(0, -1)]
9while stack:
10 u, parent = stack.pop()
11 for v in adj[u]:
12 if v == parent:
13 continue
14 if seen[v]:
15 return False
16 seen[v] = True
17 reached += 1
18 stack.append((v, u))
19return reached == n

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

A tree is an undirected graph in which every node can reach every other node, and no cycle exists. A cycle is a path of edges that starts and ends at the same node without using an edge twice.

validTree checks both parts with one search that starts at node 0. The search keeps a stack of pairs. Each pair holds a node and the node it was reached from, called its parent.

🔌 Example: Cables Between Offices

Think of the nodes as offices and the edges as cables. You walk from office to office along the cables and write down each office you reach. When you stand in office u and look at its cables, one cable leads back to the office you just came from. That cable is not a problem, because it is the way you came. If any other cable leads to an office that is already on your list, you have found a second way to reach that office, so the cables form a loop.

🔑 The Key Idea in the Code

if v == parent: continue skips the cable you came by. if seen[v]: return False handles every other cable to a seen node. Without the first line, the cable back to parent would look like a loop, and every graph that has an edge would fail. return reached == n is the second half of the check. A graph with no loop but with separate groups is not a tree.

  • Returning the edge the search meets first: the search finds the cycle at one edge, but the answer is the cycle edge that comes last in edges. For edges = [[1,2],[2,3],[3,4],[1,4],[1,5]], the search meets [2,3] first and the answer is [1,4].

  • Allocating adj with n slots: the nodes are numbered from 1 to n, so adj[n] does not exist and raises an IndexError. Use n + 1 slots and leave slot 0 empty.

  • Searching with recursion: a path of 1000 nodes plus one extra edge needs about 1000 nested calls. That reaches Python's default recursion limit of 1000 and raises a RecursionError. The explicit stack has no such limit.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks whether n nodes and a list of undirected edges make "a valid tree". A tree is a graph with no cycle in which every node is connected, so the question has two parts: no cycle, and one connected group. The cycle part is an undirected cycle check with a parent skip.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

seen[u] is True once the search has reached u, and each seen node was reached once, from its parent. Every edge scanned so far either reached a new node or led back to parent. Any other edge to a seen node returns False. At the end, reached counts the nodes the search from node 0 reached.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • if v == parent: continue: without it, the edge back to parent finds parent already in seen. validTree(2, [[0, 1]]) returns False for a graph that is a tree.

  • return reached == n: return True here accepts a forest. validTree(4, [[0, 1], [2, 3]]) returns True instead of False.

  • reached = 1 before the loop: node 0 counts as reached. With reached = 0, a tree ends one short, and Example 1 returns False.

  • n = 1, edges = []: node 0 alone is a tree. The loop does not run, and reached == n is 1 == 1, so the answer is True.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd solve this with a cycle check that remembers the parent. A tree is a graph with no cycle in which every node is connected, so I check both parts. I start one search at node 0 with a stack. Each stack entry holds a node and the node it came from. When I scan a neighbour, I first skip the node I came from, because that edge is the one I just used. If the neighbour is any other node that is already seen, there are two routes between those nodes, so there is a cycle, and I return false. Otherwise I mark the neighbour seen, count it and push it. When the stack is empty, I return whether the count of reached nodes equals n. If it is smaller, some nodes are in another group. Each node and each edge is handled a constant number of times, so time is order V plus E, and space is order V plus E for the adjacency list.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(V + E)

Look at the code. Building adj reads each of the E edges once and appends two entries, so it takes O(E). A node enters stack only when seen[v] is False, and then seen[v] becomes True. So each node is pushed at most once, and stack.pop() runs at most V times. Each pop scans adj[u] once. Over the whole run, the scans read every adjacency entry once, which is 2E entries with constant work each. Total: O(V + E).

SPACE COMPLEXITY

O(V + E)

adj holds V lists with 2E entries in total. seen holds V flags. stack holds at most V entries, because each node is pushed once. There is no recursion, so the call stack is O(1). The total is O(V + E).

Formal Recurrence Relation

T(V, E) = E (build adj) + V (pops) + 2E (adjacency entries) = O(V + E)

Derivation Progression

Build the lists

E edges, 2 appends each = O(E)

for u, v in edges reads each edge once and stores it at both ends.

Each node is pushed and popped once

seen[v] is set before the push: at most V pops

A node is pushed only when seen[v] is False, and the push sets it to True.

Each adjacency entry is read once

sum of len(adj[u]) over popped nodes = 2E

Every edge sits in two lists and each list is scanned once, with constant work per entry: the parent test, the seen test, the push.

Variable Definitions

VVV

Number of nodes, n

EEE

Number of edges, len(edges); the adjacency lists hold 2E entries in total

Memory Architecture & Bounds

🟣 Call Stack

O(1): an explicit stack, no recursion

🔵 Auxiliary Heap

O(V + E): adj has 2E entries, seen has V flags and stack never holds more than V entries

🟢 Output Space

O(1): one boolean

Boundary Best / Worst Cases

Best Case

O(V+E)O(V + E)O(V+E) for the build, but the search can stop early: the first extra edge to a seen node returns False at once.

Average Case

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

Worst Case

O(V+E)O(V + E)O(V+E): a tree, where every node is pushed and every edge is read before return reached == n.

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: "valid tree", "undirected edges". A tree is a graph with no cycle in which every node is connected, so one search from node 0 answers both parts. An edge to a seen node that is not the parent is a cycle, and the count of reached nodes shows whether the graph is connected.

CONSTRAINTS & BOUNDS

n≤2000n \le 2000n≤2000 and at most 500050005000 edges, so one search is O(V+E)O(V + E)O(V+E), about 700070007000 steps. A recursive search can be 200020002000 calls deep, which passes Python's default limit of 100010001000, so the code uses an explicit stack. There are no repeated edges and no self-loops, so each pair of nodes appears at most once.

FAANG PRODUCTION TRAPS & EDGE CASES

Dropping if v == parent: continue reports a cycle on every edge: validTree(2, [[0, 1]]) returns False. Ending with return True instead of return reached == n accepts a forest: validTree(4, [[0, 1], [2, 3]]) returns True. Starting reached at 0 undercounts by one, so Example 1 returns False.

Core Algorithmic State Invariants

  1. Example 2, One Skip And One Cycle

The search skips the edge from node 1 back to its parent 0, and it pushes nodes 2, 3 and 4 with parent 1. Popping node 3, its neighbour 2 is already seen and is not its parent 1. So the edge 3-2 closes a cycle, and the answer is false.

  1. Example 1, No Cycle

Every edge back to a parent is skipped, and no other edge reaches a seen node. The search ends with reached = 5. That equals n, so the answer is true.

  1. No Cycle Is Not Enough

In n = 4, edges = [[0,1],[2,3]], no edge closes a cycle. The search from node 0 reaches only nodes 0 and 1, so reached = 2 is not n = 4. The answer is false: the nodes form two separate groups.

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