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 & 142 Practice Problems

  • 1. Two Pointers (5 Paradigms, 25 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 (7 Paradigms, 10 Problems): Monotonic predicate partitioning, boundary searching, rotated arrays, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (4 Paradigms, 7 Problems): Bitmasking, XOR tricks, counting set bits, subset enumeration via bitmasks.
  • 4. Math & Geometry (4 Paradigms, 9 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (6 Paradigms, 15 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 (5 Paradigms, 13 Problems): Topological sort, cycle detection, Dijkstra, bipartite validation, network delay.
  • 8. Backtracking (8 Paradigms, 12 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens.
  • 9. Dynamic Programming (6 Paradigms, 13 Problems): Memoization vs tabulation, knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (6 Paradigms, 8 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (3 Paradigms, 10 Problems): Trie, Union-Find, LRU Cache, LFU Cache, Monotonic Stacks.
  • 12. Intervals & Stack / Miscellaneous (2 Paradigms, 9 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

148Items
Theory Context•Depth-First Search (DFS)
MediumLC 1372

Longest ZigZag Path in a Binary Tree (LeetCode 1372)

Top-Down Direction State: Turning Extends the Run, Going Straight Restarts It

Target Frequency:AmazonGoogleMeta

You are given the root of a binary tree. A ZigZag path starts at any node, first moves to either its left or right child, and then keeps alternating direction (left, right, left, ... or right, left, right, ...) for as long as the next child exists. Its length is the number of edges it uses, so a single node has length 0. Return the length of the longest ZigZag path in the tree.

Worked Examples

Example 1
Input:root = [1,null,1,1,1,null,null,1,1,null,1,null,null,null,1]
Output:3
Explanation: The longest ZigZag path goes right, then left, then right: 3 edges.
Example 2
Input:root = [1,1,1,null,1,null,null,1,1,null,1]
Output:4
Explanation: The longest ZigZag path goes left, right, left, right: 4 edges.
Example 3
Input:root = [1]
Output:0
Explanation: A single node has no edges to move along.

⚖️Formal Constraints & Bounds

  • The number of nodes in the tree is in the range [1, 5 * 104].

  • 1 <= Node.val <= 100

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Pass the arrival direction and current run length down. From each node, the child that turns continues the run with length + 1, and the child in the same direction starts a new run of length 1. Both calls always happen, so runs starting at every node are covered in one pass.

Real-World Scenario & Production Applications

Longest alternating decision sequence in a binary decision tree, longest run of alternating left/right moves in a branching route.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Level Ring Size Freezing

At the start of each level iteration, freeze level_size = len(queue) to isolate the current depth ring from newly appended child nodes.

Mathematical Recurrence / Code Invariant
queue = deque([root])
while queue:
    level_size = len(queue)  # Freeze ring count
    current_level = []
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1if not node: return
2longest = max(longest, length)
3if went_left: dfs(node.right, False, length + 1); dfs(node.left, True, 1)
4else: dfs(node.left, True, length + 1); dfs(node.right, False, 1)
5dfs(root.left, True, 1); dfs(root.right, False, 1)

Target: Longest ZigZag Path in a Binary Tree (LeetCode 1372). Each call knows the arrival direction and the current run length

Boundary Model: Depth-First Recursion / Call Stack Frame Lifecycle

Subtree bottom-up aggregation: a node combines child subtree solutions upon backtrack; top-down invariants pass accumulated state down the path.

Loop Invariant Termination

Base case: if not node: return base; recursive calls on left/right child nodes.

Conceptual Narrative

🧠 Pattern Intuition & Real-World Analogy

Count Good Nodes (§4.4) passes the path's maximum down to each child. ZigZag passes down something smaller but stranger: which way you just came from, and how long your current alternating run is. Each node decides its children's state without ever looking back up.

⚡ The Analogy: A Ski Slalom Where Every Gate Must Switch Sides

A skier runs a slalom course laid out like a tree of gates. A run only counts while each gate is passed on the opposite side from the one before. At every gate the skier knows two things: which side they took the last gate on, and how many alternating gates they have strung together. Switching sides extends the run by one. Taking the same side twice doesn't end the day; it simply starts a fresh run that already counts that one gate.

🪄 Breaking Down the Code's "Magic Trick"
Code / Blueprint
def dfs(node, went_left, length):
nonlocal longest
if not node:
return
longest = max(longest, length)
if went_left:
dfs(node.right, False, length + 1) # turn: the zigzag grows
dfs(node.left, True, 1) # same direction: a new zigzag starts at this edge
else:
dfs(node.left, True, length + 1)
dfs(node.right, False, 1)
 

Every node launches both options at once: the child that continues the zigzag inherits length + 1, and the child that breaks it starts over at 1. Because both calls always happen, zigzags starting at every node in the tree are explored in the same single pass, with no outer loop over starting points.

💡 Summary

When a path's validity depends on the direction of the previous step, pass that direction down as a parameter together with the current run length, and let every node spawn both the "continue" and the "restart" branch. One top-down pass then covers runs that start anywhere in the tree.

  • Restarting at 0 instead of 1: Restarting at 0 instead of 1 undercounts every run that starts below the root ([1,2,null,3,null,null,4] has answer 2, but a reset to 0 reports 1).

  • Only following zigzags from the root: Only following zigzags that start at the root misses runs that start deeper.

  • Counting nodes instead of edges: A single node has length 0, because length counts edges, not nodes.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how passing the arrival direction and run length down lets one pass cover zigzags starting anywhere.

Pattern Recognition Signals

The 10-second spot

Longest path that alternates left and right, starting at any node, measured in edges.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: length counts the alternating edges ending at this node; turning adds one, repeating a direction resets to 1.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Resetting to 0 instead of 1: the edge that breaks the old run is the first edge of the new one.

  • Only starting from the root: zigzags can begin at any node.

  • Counting nodes: a single node has length 0.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I call dfs(node, went_left, length), which returns on None and updates a global longest. If I arrived by a left edge, the right child continues the zigzag with length + 1 and the left child starts a new one with 1; otherwise it's the mirror image. I seed it with dfs(root.left, True, 1) and dfs(root.right, False, 1). Every node is visited once: O(N) time, O(H) stack.

So: record length; if you arrived by a left edge, go right with length + 1 and left with 1 (and the mirror image otherwise).

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the recursive function: `def maxDepth(node):`. For every node in the binary tree, the function executes exactly 2 recursive calls (`maxDepth(node.left)` and `maxDepth(node.right)`) and performs O(1) constant-time arithmetic combining their results (`1 + max(left, right)`). Because each of the N tree nodes is visited and processed exactly once, total time is N · O(1) = O(N).

SPACE COMPLEXITY

O(H) Auxiliary

Look at the recursion call stack: Each recursive call adds a new stack frame storing the local `node` pointer. The maximum number of simultaneous stack frames alive at any instant equals the maximum path depth from root to leaf, which is the tree height H. In a balanced binary tree, H = log2(N) -> O(log N) stack memory. In a worst-case skewed tree (linked list), H = N -> O(N) stack memory. We allocate zero heap objects, so auxiliary space is bounded by tree height O(H).

Formal Recurrence Relation

T(N) = 2T(N/2) + O(1) ⟹ O(N)

Look at the recursive function: def maxDepth(node):. For every node in the binary tree, the function executes exactly 2 recursive calls (maxDepth(node.left) and maxDepth(node.right)) and performs O(1) constant-time arithmetic combining their results (1 + max(left, right)). Because each of the N tree nodes is visited and processed exactly once, total time is N · O(1) = O(N).

Derivation Progression

Node Visitation

N nodes visited

Every node in the binary tree is entered and exited exactly once.

O(1) Work Per Frame

1 + max(left, right)

Combining child subproblem depths requires a single addition and max comparison in O(1).

Tree Recurrence

T(N) = 2T(N/2) + O(1) ⟹ O(N)

Visiting all N nodes with constant combining work yields strictly O(N) time.

Variable Definitions

NNN

Total number of nodes in the binary tree

HHH

Tree height (log N in balanced tree, N in skewed tree)

Memory Architecture & Bounds

🟣 Call Stack

O(H) Recursion stack storing tree height H frames (O(log N) balanced, O(N) skewed)

🔵 Auxiliary Heap

O(1) Zero heap allocations

🟢 Output Space

O(1) Returns integer tree depth

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N) time with O(log⁡N)O(\log N)O(logN) stack on balanced tree.

Average Case

O(N)O(N)O(N) time with O(log⁡N)O(\log N)O(logN) stack.

Worst Case

O(N)O(N)O(N) time with O(N)O(N)O(N) stack on skewed tree (linked list shape).

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: **"longest ZigZag path"**, **"alternate left and right"**, **"start from any node"**. Top-down parameter passing with a direction state: each call is told which way it arrived and how long the current alternating run is.

CONSTRAINTS & BOUNDS

N≤5×104N \le 5 \times 10^4N≤5×104. One pass: O(N)O(N)O(N) time.

FAANG PRODUCTION TRAPS & EDGE CASES

Restarting at 0 instead of 1 undercounts every run that starts below the root ([1,2,null,3,null,null,4] has answer 2, but a reset to 0 reports 1). Only following zigzags that start at the root misses runs that start deeper. A single node has length 0, because length counts edges, not nodes.

Core Algorithmic State Invariants

1. Subtree Independence Invariant

Each recursive invocation evaluates disjoint subtrees independently, synthesizing solutions bottom-up from child return values without cross-branch interference.

2. Post-Order Composition Invariant

Node state is computed from child results (e.g. 1 + max(L, R) for height, or sentinel -1 when balanced condition |L - R| <= 1 fails), bubbling answers toward the root.

3. Base Case Identity Termination

Null nodes return mathematical identity values (0 for height/count, True for balance, None for LCA), guaranteeing clean recursion termination in O(H) stack space.

Theory Context•Depth-First Search (DFS)
MediumLC 1372

Longest ZigZag Path in a Binary Tree (LeetCode 1372)

Top-Down Direction State: Turning Extends the Run, Going Straight Restarts It

Target Frequency:AmazonGoogleMeta

You are given the root of a binary tree. A ZigZag path starts at any node, first moves to either its left or right child, and then keeps alternating direction (left, right, left, ... or right, left, right, ...) for as long as the next child exists. Its length is the number of edges it uses, so a single node has length 0. Return the length of the longest ZigZag path in the tree.

Worked Examples

Example 1
Input:root = [1,null,1,1,1,null,null,1,1,null,1,null,null,null,1]
Output:3
Explanation: The longest ZigZag path goes right, then left, then right: 3 edges.
Example 2
Input:root = [1,1,1,null,1,null,null,1,1,null,1]
Output:4
Explanation: The longest ZigZag path goes left, right, left, right: 4 edges.
Example 3
Input:root = [1]
Output:0
Explanation: A single node has no edges to move along.

⚖️Formal Constraints & Bounds

  • The number of nodes in the tree is in the range [1, 5 * 104].

  • 1 <= Node.val <= 100

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Pass the arrival direction and current run length down. From each node, the child that turns continues the run with length + 1, and the child in the same direction starts a new run of length 1. Both calls always happen, so runs starting at every node are covered in one pass.

Real-World Scenario & Production Applications

Longest alternating decision sequence in a binary decision tree, longest run of alternating left/right moves in a branching route.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Level Ring Size Freezing

At the start of each level iteration, freeze level_size = len(queue) to isolate the current depth ring from newly appended child nodes.

Mathematical Recurrence / Code Invariant
queue = deque([root])
while queue:
    level_size = len(queue)  # Freeze ring count
    current_level = []
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1if not node: return
2longest = max(longest, length)
3if went_left: dfs(node.right, False, length + 1); dfs(node.left, True, 1)
4else: dfs(node.left, True, length + 1); dfs(node.right, False, 1)
5dfs(root.left, True, 1); dfs(root.right, False, 1)

Target: Longest ZigZag Path in a Binary Tree (LeetCode 1372). Each call knows the arrival direction and the current run length

Boundary Model: Depth-First Recursion / Call Stack Frame Lifecycle

Subtree bottom-up aggregation: a node combines child subtree solutions upon backtrack; top-down invariants pass accumulated state down the path.

Loop Invariant Termination

Base case: if not node: return base; recursive calls on left/right child nodes.

Conceptual Narrative

🧠 Pattern Intuition & Real-World Analogy

Count Good Nodes (§4.4) passes the path's maximum down to each child. ZigZag passes down something smaller but stranger: which way you just came from, and how long your current alternating run is. Each node decides its children's state without ever looking back up.

⚡ The Analogy: A Ski Slalom Where Every Gate Must Switch Sides

A skier runs a slalom course laid out like a tree of gates. A run only counts while each gate is passed on the opposite side from the one before. At every gate the skier knows two things: which side they took the last gate on, and how many alternating gates they have strung together. Switching sides extends the run by one. Taking the same side twice doesn't end the day; it simply starts a fresh run that already counts that one gate.

🪄 Breaking Down the Code's "Magic Trick"
Code / Blueprint
def dfs(node, went_left, length):
nonlocal longest
if not node:
return
longest = max(longest, length)
if went_left:
dfs(node.right, False, length + 1) # turn: the zigzag grows
dfs(node.left, True, 1) # same direction: a new zigzag starts at this edge
else:
dfs(node.left, True, length + 1)
dfs(node.right, False, 1)
 

Every node launches both options at once: the child that continues the zigzag inherits length + 1, and the child that breaks it starts over at 1. Because both calls always happen, zigzags starting at every node in the tree are explored in the same single pass, with no outer loop over starting points.

💡 Summary

When a path's validity depends on the direction of the previous step, pass that direction down as a parameter together with the current run length, and let every node spawn both the "continue" and the "restart" branch. One top-down pass then covers runs that start anywhere in the tree.

  • Restarting at 0 instead of 1: Restarting at 0 instead of 1 undercounts every run that starts below the root ([1,2,null,3,null,null,4] has answer 2, but a reset to 0 reports 1).

  • Only following zigzags from the root: Only following zigzags that start at the root misses runs that start deeper.

  • Counting nodes instead of edges: A single node has length 0, because length counts edges, not nodes.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how passing the arrival direction and run length down lets one pass cover zigzags starting anywhere.

Pattern Recognition Signals

The 10-second spot

Longest path that alternates left and right, starting at any node, measured in edges.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: length counts the alternating edges ending at this node; turning adds one, repeating a direction resets to 1.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Resetting to 0 instead of 1: the edge that breaks the old run is the first edge of the new one.

  • Only starting from the root: zigzags can begin at any node.

  • Counting nodes: a single node has length 0.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I call dfs(node, went_left, length), which returns on None and updates a global longest. If I arrived by a left edge, the right child continues the zigzag with length + 1 and the left child starts a new one with 1; otherwise it's the mirror image. I seed it with dfs(root.left, True, 1) and dfs(root.right, False, 1). Every node is visited once: O(N) time, O(H) stack.

So: record length; if you arrived by a left edge, go right with length + 1 and left with 1 (and the mirror image otherwise).

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the recursive function: `def maxDepth(node):`. For every node in the binary tree, the function executes exactly 2 recursive calls (`maxDepth(node.left)` and `maxDepth(node.right)`) and performs O(1) constant-time arithmetic combining their results (`1 + max(left, right)`). Because each of the N tree nodes is visited and processed exactly once, total time is N · O(1) = O(N).

SPACE COMPLEXITY

O(H) Auxiliary

Look at the recursion call stack: Each recursive call adds a new stack frame storing the local `node` pointer. The maximum number of simultaneous stack frames alive at any instant equals the maximum path depth from root to leaf, which is the tree height H. In a balanced binary tree, H = log2(N) -> O(log N) stack memory. In a worst-case skewed tree (linked list), H = N -> O(N) stack memory. We allocate zero heap objects, so auxiliary space is bounded by tree height O(H).

Formal Recurrence Relation

T(N) = 2T(N/2) + O(1) ⟹ O(N)

Look at the recursive function: def maxDepth(node):. For every node in the binary tree, the function executes exactly 2 recursive calls (maxDepth(node.left) and maxDepth(node.right)) and performs O(1) constant-time arithmetic combining their results (1 + max(left, right)). Because each of the N tree nodes is visited and processed exactly once, total time is N · O(1) = O(N).

Derivation Progression

Node Visitation

N nodes visited

Every node in the binary tree is entered and exited exactly once.

O(1) Work Per Frame

1 + max(left, right)

Combining child subproblem depths requires a single addition and max comparison in O(1).

Tree Recurrence

T(N) = 2T(N/2) + O(1) ⟹ O(N)

Visiting all N nodes with constant combining work yields strictly O(N) time.

Variable Definitions

NNN

Total number of nodes in the binary tree

HHH

Tree height (log N in balanced tree, N in skewed tree)

Memory Architecture & Bounds

🟣 Call Stack

O(H) Recursion stack storing tree height H frames (O(log N) balanced, O(N) skewed)

🔵 Auxiliary Heap

O(1) Zero heap allocations

🟢 Output Space

O(1) Returns integer tree depth

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N) time with O(log⁡N)O(\log N)O(logN) stack on balanced tree.

Average Case

O(N)O(N)O(N) time with O(log⁡N)O(\log N)O(logN) stack.

Worst Case

O(N)O(N)O(N) time with O(N)O(N)O(N) stack on skewed tree (linked list shape).

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: **"longest ZigZag path"**, **"alternate left and right"**, **"start from any node"**. Top-down parameter passing with a direction state: each call is told which way it arrived and how long the current alternating run is.

CONSTRAINTS & BOUNDS

N≤5×104N \le 5 \times 10^4N≤5×104. One pass: O(N)O(N)O(N) time.

FAANG PRODUCTION TRAPS & EDGE CASES

Restarting at 0 instead of 1 undercounts every run that starts below the root ([1,2,null,3,null,null,4] has answer 2, but a reset to 0 reports 1). Only following zigzags that start at the root misses runs that start deeper. A single node has length 0, because length counts edges, not nodes.

Core Algorithmic State Invariants

1. Subtree Independence Invariant

Each recursive invocation evaluates disjoint subtrees independently, synthesizing solutions bottom-up from child return values without cross-branch interference.

2. Post-Order Composition Invariant

Node state is computed from child results (e.g. 1 + max(L, R) for height, or sentinel -1 when balanced condition |L - R| <= 1 fails), bubbling answers toward the root.

3. Base Case Identity Termination

Null nodes return mathematical identity values (0 for height/count, True for balance, None for LCA), guaranteeing clean recursion termination in O(H) stack space.

Rosetta Dual-Monaco ComparisonPython 3
CANONICAL INVARIANT TEMPLATE
Loading...
CONCRETE: LONGEST ZIGZAG PATH IN A BINARY TREE (LEETCODE 1372)
T = O(N)S = O(H) Auxiliary
Loading...
Pattern Implementation Mapping TableCanonical Invariant ⟷ Concrete Code ⟷ Engineering Rationale
Canonical InvariantConcrete CodeEngineering Rationale
def dfs_direction_state(node, went_left, length, best)def dfs(node, went_left, length)Each call knows the arrival direction and the current run length
best[0] = max(best[0], length)longest = max(longest, length)Every node is a possible end of a zigzag
dfs_direction_state(node.right, False, length + 1, best)dfs(node.right, False, length + 1)Alternating extends the run by one edge
dfs_direction_state(node.left, True, 1, best)dfs(node.left, True, 1)A new run starts, and its first edge already counts
(caller) dfs_direction_state(root.left, True, 1, best); dfs_direction_state(root.right, False, 1, best)dfs(root.left, True, 1); dfs(root.right, False, 1)The root's two edges each start a run of length 1
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•