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•Depth-First Search (DFS)

Builds on Bottom-Up DFS, which you haven’t finished yet.

P2EasyBuilt on Bottom-Up DFS~3 min speedrun

Two-Tree DFS (Walking a Pair of Nodes Together)

Built on
Bottom-Up DFSThe call takes a pair of nodes, one from each tree, and combines the pair with the answers of its two child pairs.
The problem

Same Tree (LC 100)(opens leetcode.com)

Two binary trees are given by their root nodes, p and q. Either tree may be empty. The trees are the same when they have the same shape and, at every position, the two nodes hold the same value.

Same shape means that wherever one tree has a left child, the other tree has a left child too, and the same for right children. Return True when the trees are the same and False otherwise.

Example 1
Input:p = [1,2,3], q = [1,2,3]
Output:true
Both trees have a root 1 with a left child 2 and a right child 3.
p
213
q
213

Two Tree Walk

two roots ➔ pair the children by position ➔ combine the pair with its two child pairs' answers
Spot it when: The problem gives you two trees and asks whether they match, mirror each other or merge into one: the answer for a pair of nodes, one from each tree, comes from the pair itself and the answers for the pairs of their children.
Two Tree Walk
Python
1from typing import Callable, Optional, TypeVar
2 
3R = TypeVar("R")
4 
5class TreeNode:
6 def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None):
7 self.val = val
8 self.left = left
9 self.right = right
10 
11 
12def walk_pair(first: Optional[TreeNode], second: Optional[TreeNode], end: Callable[[Optional[TreeNode], Optional[TreeNode]], R], combine: Callable[[TreeNode, TreeNode, R, R], R], mirror: bool = False) -> R:
13 """Two Tree Walk: the answer for the pair of trees rooted at first and second.
14 end(a, b) is the answer for a pair where a or b is missing. combine(a, b, left,
15 right) builds the answer for two present nodes from the answers of their two child
16 pairs.
17 Precondition: a pair's answer depends only on its two nodes and on the answers of
18 its two child pairs.
19 mirror=False pairs left with left and right with right. mirror=True crosses them:
20 a.left with b.right, a.right with b.left.
21 N = number of pairs of present nodes the walk reaches. H = depth of the walk. T =
22 the cost of one end or combine call.
23 Time: O(N * T) | Space: O(H) recursion stack
24 """
25 def solve(a: Optional[TreeNode], b: Optional[TreeNode]) -> R:
26 # 1. Base case: a pair with a missing node ends the walk
27 if not a or not b:
28 return end(a, b)
29 
30 # 2. Divide: pair the children (crossed for a mirror walk), then solve each pair
31 # on its own
32 partner_of_left, partner_of_right = (b.right, b.left) if mirror else (b.left, b.right)
33 left = solve(a.left, partner_of_left)
34 right = solve(a.right, partner_of_right)
35 
36 # 3. Combine: this pair's answer comes only from its two nodes and its two child
37 # pairs' answers
38 return combine(a, b, left, right)
39 
40 return solve(first, second)
Invariant: isSameTree(p, q) returns True exactly when the subtrees at p and q have the same shape and the same values, built only from p.val == q.val and the answers for the two child pairs.

Two trees match only if they match position by position, so walk them together: isSameTree(p, q) takes one node from each tree and pairs the children by position, p.left with q.left and p.right with q.right. Each call answers for the pair of subtrees below p and q, and that answer depends only on p.val == q.val and the two child answers, so every position is visited once and neither tree is read alone. Which children to pair is the whole design: Symmetric Tree (LC 101) crosses them, p.left with q.right, and Merge Two Binary Trees (LC 617) keeps them in place. The trap is a pair with one missing node. It must end the walk with False, so the base case is if not p or not q: return p is None and q is None. Returning True there treats a missing node as a match, and [1,2] and [1,null,2] pass as the same tree.

See it run

Two Tree Walk, step by step

pair now (1, 1) · returns - · answer not yet

pair now (2, 2) · returns - · answer not yet

pair now (4, none) · returns - · answer not yet

pair now - · returns False · answer not yet

pair now (none, 4) · returns - · answer not yet

pair now - · returns False · answer not yet

pair now - · returns False · answer not yet

pair now (3, 3) · returns - · answer not yet

pair now - · returns True · answer not yet

pair now - · returns False · answer not yet

pair now - · returns False · answer False

The tree p is on the left and the tree q is on the right. Each call looks at one node of p and the node at the same position of q.

  • now the pair the running call is looking at
  • waiting a pair whose call waits for its child pairs
  • same a pair whose call returned True
  • differs a pair whose call returned False

Call isSameTree on the pair (1, 1). The call waits for the answers of its two child pairs.

Call isSameTree on the pair (2, 2). The call waits for the answers of its two child pairs.

Call isSameTree on the pair (4, none). The tree q has no node at this position.

Only one node exists here, so the shapes differ. The base case returns p is None and q is None, which is False.

Call isSameTree on the pair (none, 4). The tree p has no node at this position.

Only one node exists here, so the shapes differ. The base case returns p is None and q is None, which is False.

The values 2 and 2 are equal. The left pair returned False and the right pair returned False, so the call returns False.

Call isSameTree on the pair (3, 3). The call waits for the answers of its two child pairs.

The values 3 and 3 are equal. The left pair returned True (both children are missing) and the right pair returned True (both children are missing), so the call returns True.

The values 1 and 1 are equal. The left pair returned False and the right pair returned True, so the call returns False.

The call on the two roots returned False, so the trees are not the same.

421now3241now3
calls waiting (p value, q value)
1/1
finished pairs
empty
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Returning True when only one of the two nodes is missing

    Mistake
    return True
    Fix
    return p is None and q is None

    What happens: A pair with a missing node ends the walk. When both nodes are missing, the two trees end at the same place, so True is correct. When only one node is missing, one tree has a node where the other has none, so the shapes differ and the answer must be False. return True reports a match in both cases. On p = [1,2] and q = [1,null,2], the pair (p.left, q.left) is (2, None) and the pair (p.right, q.right) is (None, 2). Both pairs return True, so two different trees are reported as the same.

    Example: Same Tree (LC 100), Example 2: p = [1,2], q = [1,null,2]
    Mistake: returns TrueFix: returns False
  2. 2

    Gotcha 2: Reading p.val before checking that both nodes exist

    Mistake
    if p.val != q.val:
        return False
    if not p or not q:
        return p is None and q is None
    Fix
    if not p or not q:
        return p is None and q is None

    What happens: A missing node is None, and None has no val. When one tree is shorter, a missing node reaches the first line and q.val raises an AttributeError. This also happens at every leaf, because the two missing children of a leaf make the pair (None, None). Check for a missing node first, then read val. On p = [1,2] and q = [1], the pair (p.left, q.left) is (2, None), so the call crashes instead of returning False.

    Example: p = [1,2], q = [1]
    Mistake: raises AttributeErrorFix: returns False
  3. 3

    Gotcha 3: Comparing child nodes with == instead of walking them

    Mistake
    left_same = p.left == q.left
    right_same = p.right == q.right
    Fix
    left_same = isSameTree(p.left, q.left)
    right_same = isSameTree(p.right, q.right)

    What happens: p.left == q.left asks whether both names point to the same node object. Two separate trees never share nodes, so the answer is False for every pair of real children, even when their values and shapes are equal. The walk never reaches the children's values. On p = [1,2,3] and q = [1,2,3], the root pair fails at p.left == q.left and the call returns False.

    Example: Same Tree (LC 100), Example 1: p = [1,2,3], q = [1,2,3]
    Mistake: returns FalseFix: returns True
  4. 4

    Gotcha 4: Comparing the two lists of values instead of walking the trees together

    Mistake
    return preorder(p) == preorder(q)
    Fix
    return isSameTree(p, q)

    What happens: A list of values, such as the preorder list, forgets where the missing children are. Two trees with different shapes can give the same list. For p = [1,2,2,3] and q = [1,2,2,null,3], both preorder lists are [1, 2, 3, 2], but 3 is the left child of the left 2 in p and the right child of the left 2 in q. A list comparison says True. The pair walk compares the left children of the left 2 as one pair and finds that one of them is missing, so it returns False.

    Example: p = [1,2,2,3], q = [1,2,2,null,3]
    Mistake: returns TrueFix: returns False
Not this one if…
  • Both p and q are nodes of one tree, as in Lowest Common Ancestor of a Binary Tree (LC 236): there is only one tree to walk, so use Bottom-Up DFS, where each call returns what its subtree found. See Bottom-Up DFS

Deep Dive

Why a call can trust the answers of its child pairs

isSameTree(p, q) is defined by one sentence: it returns True exactly when the subtree at p and the subtree at q have the same shape and the same values. The code only has to keep that sentence true for one call, and the sentence then holds for every call.

Two missing nodes are two empty subtrees, and two empty subtrees are the same. So return p is None and q is None gives True there. One missing node and one real node cannot be the same, because one subtree has a node where the other has none. The same line gives False. For two real nodes, the subtrees are the same exactly when the two values are equal, the left subtrees are the same and the right subtrees are the same. The two child calls answer the last two parts, so the call only compares the two values.

Take p = [1, 2, 3, 4] and q = [1, 2, 3, null, 4]. In both trees the node 4 hangs under the node 2. It is the left child in p and the right child in q. The call on the pair (2, 2) makes two calls: the pair (4, None) and the pair (None, 4). Each pair has one missing node and returns False. So the pair (2, 2) returns False, even though its two values are equal. The pair (3, 3) returns True. The call on the roots receives False and True, and returns False.

The walk makes 7 calls in all: 5 calls on pairs where at least one node exists, and 2 calls below the pair (3, 3), where both nodes are missing. The two trees have the same preorder list of values, [1, 2, 4, 3]. A comparison of the two lists would answer True. The walk answers False because it compares positions.

Which children to pair: the same side, crossed, or both ways

The card's walk pairs p.left with q.left and p.right with q.right. That is one choice. The rest of the walk is the same for every problem on this card: a base case for a pair with a missing node, two calls on child pairs, and a combine step. Only the pairing and the combine change.

Symmetric Tree (LC 101) crosses the pairs. A mirror pairs the outer nodes and the inner nodes, so the walk starts on the root's two children and calls is_mirror(a.left, b.right) and is_mirror(a.right, b.left). In the card's template this is mirror=True. For root = [1, 2, 2, null, 3, null, 3] the answer is False: the left 2 has only a right child, and the right 2 has only a right child, so the right child of the left 2 faces a missing node. Pairing the same side instead answers True for this tree.

Merge Two Binary Trees (LC 617) builds a tree. When one node of a pair is missing, the base case returns the node that exists, a or b, and that whole branch joins the result. The combine builds TreeNode(a.val + b.val, left, right). For root1 = [1, 3, 2, 5] and root2 = [2, 1, 3, null, 4, null, 7] the result is [3, 4, 5, 5, 4, null, 7]. If the base case returned None for a pair with one missing node, as Same Tree does, the result would shrink to [3, 4, 5], because every branch that exists in only one tree would be lost.

Flip Equivalent Binary Trees (LC 951) accepts either pairing at every node. The combine tries the same side first and the crossed pairs second: the same-side pairs both match, or the crossed pairs both match. The node values are unique in this problem. For that reason only one of the two pairings can match the child values, and the other fails after one comparison. The cost stays at O(min(N, M)).

Subtree of Another Tree: the walk as a check at every node

Subtree of Another Tree (LC 572) asks whether subRoot appears inside root. Run the card's walk on the pair (node, subRoot) for every node of root, and stop at the first True. The walk is the check. The outer loop is an ordinary tree walk.

The check needs the full base case. Suppose the walk returned True as soon as subRoot was used up. Then root = [3, 4, 5, 1, 2, null, null, null, null, 0] would match subRoot = [4, 1, 2], because the subtree of the node 4 starts with 4, 1, 2. But the node 2 in root has an extra child 0, so the answer is False. With the card's base case, the pair (0, None) returns False, and the extra node is seen.

The cost is O(N * M) in the worst case. The walk runs at each of the N nodes of root, and it never goes past the smaller tree, so one walk costs at most O(M), where M is the number of nodes in subRoot. Giving every distinct subtree an id lets one pass answer the question, but it needs a different idea and is not part of this card.

The cost, and where the walk stops

A call on a pair where both nodes exist makes two more calls. A call on a pair with a missing node makes none. So the number of calls is at most 2 * min(N, M) + 1. For two identical trees of 7 nodes it is exactly 15. Each call does constant work, so the time is O(min(N, M)).

The recursion goes down only while both nodes exist. The stack holds at most one frame per level of the shallower tree, so the space is O(H), where H is the height of the shallower tree.

The blueprint calls both children before it compares the values. A difference at the root therefore does not stop the walk. A version that returns False as soon as p.val != q.val stops earlier on trees that differ near the root. The worst case is the same, because two identical trees must be walked to the end.

A chain of 1000 nodes goes past Python's default limit of about 1000 nested calls. The explicit-stack version keeps the pairs on its own list. It starts with stack = [(p, q)], pops a pair, returns False on a mismatch, and pushes (a.left, b.left) and (a.right, b.right).

60-Second Pitch

"The question is about two trees, so I use a two-tree walk: one recursive call holds a node from each tree. isSameTree(p, q) pairs the children by position, p.left with q.left and p.right with q.right. If a node is missing on either side, the trees match here only when both are missing, so the base case is return p is None and q is None. Otherwise the pair matches when the two values are equal and both child pairs match. It is correct because each call can trust the two answers it receives: they are the true answers for smaller pairs of subtrees. Each position is visited once, so the time is O(min(N, M)) and the stack is as deep as the shallower tree. For Symmetric Tree, I cross the pairs. For very deep trees, I use a stack of pairs."

Common Questions

How is Two Tree Walk different from Bottom-Up DFS?

Bottom-Up DFS holds one node per call and builds the answer from the answers of its two subtrees. Two Tree Walk holds a pair of nodes, one from each tree, and builds the answer from the pair and from the answers of two child pairs. The base case also has more outcomes: two missing nodes, one missing node, and two real nodes.

Why must a pair with one missing node return False in Same Tree?

A node that exists in one tree and not in the other means the shapes differ at that position. Returning True would hide that difference. On [1, 2] and [1, null, 2] the walk would then report two different trees as the same.

How do you check that a tree is symmetric?

Start the walk on the root's left child and right child, and cross the pairs: the outer pair is (a.left, b.right) and the inner pair is (a.right, b.left). The base case and the combine step are the same as in Same Tree. Symmetric Tree (LC 101) is this walk with mirror=True.

What is the cost of Subtree of Another Tree (LC 572) with this walk?

O(N * M) in the worst case. The walk runs on (node, subRoot) for each of the N nodes of root, and one walk costs at most O(M), because it never goes past the smaller tree. The base case must return False for a pair with one missing node, so that extra nodes below a match are seen.

How do you walk two trees without recursion?

Keep a stack of pairs. Start with [(p, q)]. Pop a pair. If one node is missing and the other exists, return False. If both exist and the values differ, return False. Otherwise push (a.left, b.left) and (a.right, b.right). If the stack empties, return True.

SWE Reasoning

Spot it

  • The statement gives you two trees, p and q, or one tree that you compare with itself.
  • The question is whether they are the same, mirror images, or equal after flips, or the answer is a new tree built from both.
  • The answer for a pair of nodes depends only on the two nodes and on the answers for the pairs of their children.
  • You can say in one sentence what solve(p, q) returns for the subtrees at p and q.
  • Only one tree is walked, or the answer depends on what lies above a node: use Bottom-Up DFS or Top-Down DFS instead.

The invariant, step by step

  1. Define in one sentence what solve(p, q) returns: the answer for the subtree at p and the subtree at q.
  2. The base case handles a pair with a missing node. Two missing nodes are two empty subtrees. One missing node means the shapes differ.
  3. For two real nodes, the two child calls run first. Each pairs a child of p with its partner in q, and by the sentence in step 1 each returns a finished answer.
  4. The combine step builds this pair's answer from the two nodes and the two child answers. It does not look any deeper.
  5. So the sentence is true for this call and for every call. The call on the two roots returns the answer for the whole pair of trees.

Traps

  • Pairing the wrong children. Same Tree pairs the same side. Symmetric Tree (LC 101) crosses the pairs. Pairing the same side for a mirror question accepts [1, 2, 2, null, 3, null, 3], which is not symmetric.
  • Losing a branch that exists in one tree only. In Merge Two Binary Trees (LC 617) a pair with one missing node must return the node that exists. Returning None shrinks the result to [3, 4, 5] on the LeetCode example.
  • Stopping when the smaller tree is used up. In Subtree of Another Tree (LC 572), a base case that returns True when subRoot ends accepts a tree that has extra nodes below the match.
  • A deep chain. Two chains of 1000 nodes pass Python's default limit of about 1000 nested calls. Use an explicit stack of pairs.

Practice

Canonical LeetCode:Same Tree (LC 100)Symmetric Tree (LC 101)Merge Two Binary Trees (LC 617)Subtree of Another Tree (LC 572)Flip Equivalent Binary Trees (LC 951)
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS, which you haven’t finished yet.

P2EasyBuilt on Bottom-Up DFS~3 min speedrun

Two-Tree DFS (Walking a Pair of Nodes Together)

Built on
Bottom-Up DFSThe call takes a pair of nodes, one from each tree, and combines the pair with the answers of its two child pairs.
The problem

Same Tree (LC 100)(opens leetcode.com)

Two binary trees are given by their root nodes, p and q. Either tree may be empty. The trees are the same when they have the same shape and, at every position, the two nodes hold the same value.

Same shape means that wherever one tree has a left child, the other tree has a left child too, and the same for right children. Return True when the trees are the same and False otherwise.

Example 1
Input:p = [1,2,3], q = [1,2,3]
Output:true
Both trees have a root 1 with a left child 2 and a right child 3.
p
213
q
213

Two Tree Walk

two roots ➔ pair the children by position ➔ combine the pair with its two child pairs' answers
Spot it when: The problem gives you two trees and asks whether they match, mirror each other or merge into one: the answer for a pair of nodes, one from each tree, comes from the pair itself and the answers for the pairs of their children.
Two Tree Walk
Python
1from typing import Callable, Optional, TypeVar
2 
3R = TypeVar("R")
4 
5class TreeNode:
6 def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None):
7 self.val = val
8 self.left = left
9 self.right = right
10 
11 
12def walk_pair(first: Optional[TreeNode], second: Optional[TreeNode], end: Callable[[Optional[TreeNode], Optional[TreeNode]], R], combine: Callable[[TreeNode, TreeNode, R, R], R], mirror: bool = False) -> R:
13 """Two Tree Walk: the answer for the pair of trees rooted at first and second.
14 end(a, b) is the answer for a pair where a or b is missing. combine(a, b, left,
15 right) builds the answer for two present nodes from the answers of their two child
16 pairs.
17 Precondition: a pair's answer depends only on its two nodes and on the answers of
18 its two child pairs.
19 mirror=False pairs left with left and right with right. mirror=True crosses them:
20 a.left with b.right, a.right with b.left.
21 N = number of pairs of present nodes the walk reaches. H = depth of the walk. T =
22 the cost of one end or combine call.
23 Time: O(N * T) | Space: O(H) recursion stack
24 """
25 def solve(a: Optional[TreeNode], b: Optional[TreeNode]) -> R:
26 # 1. Base case: a pair with a missing node ends the walk
27 if not a or not b:
28 return end(a, b)
29 
30 # 2. Divide: pair the children (crossed for a mirror walk), then solve each pair
31 # on its own
32 partner_of_left, partner_of_right = (b.right, b.left) if mirror else (b.left, b.right)
33 left = solve(a.left, partner_of_left)
34 right = solve(a.right, partner_of_right)
35 
36 # 3. Combine: this pair's answer comes only from its two nodes and its two child
37 # pairs' answers
38 return combine(a, b, left, right)
39 
40 return solve(first, second)
Invariant: isSameTree(p, q) returns True exactly when the subtrees at p and q have the same shape and the same values, built only from p.val == q.val and the answers for the two child pairs.

Two trees match only if they match position by position, so walk them together: isSameTree(p, q) takes one node from each tree and pairs the children by position, p.left with q.left and p.right with q.right. Each call answers for the pair of subtrees below p and q, and that answer depends only on p.val == q.val and the two child answers, so every position is visited once and neither tree is read alone. Which children to pair is the whole design: Symmetric Tree (LC 101) crosses them, p.left with q.right, and Merge Two Binary Trees (LC 617) keeps them in place. The trap is a pair with one missing node. It must end the walk with False, so the base case is if not p or not q: return p is None and q is None. Returning True there treats a missing node as a match, and [1,2] and [1,null,2] pass as the same tree.

See it run

Two Tree Walk, step by step

pair now (1, 1) · returns - · answer not yet

pair now (2, 2) · returns - · answer not yet

pair now (4, none) · returns - · answer not yet

pair now - · returns False · answer not yet

pair now (none, 4) · returns - · answer not yet

pair now - · returns False · answer not yet

pair now - · returns False · answer not yet

pair now (3, 3) · returns - · answer not yet

pair now - · returns True · answer not yet

pair now - · returns False · answer not yet

pair now - · returns False · answer False

The tree p is on the left and the tree q is on the right. Each call looks at one node of p and the node at the same position of q.

  • now the pair the running call is looking at
  • waiting a pair whose call waits for its child pairs
  • same a pair whose call returned True
  • differs a pair whose call returned False

Call isSameTree on the pair (1, 1). The call waits for the answers of its two child pairs.

Call isSameTree on the pair (2, 2). The call waits for the answers of its two child pairs.

Call isSameTree on the pair (4, none). The tree q has no node at this position.

Only one node exists here, so the shapes differ. The base case returns p is None and q is None, which is False.

Call isSameTree on the pair (none, 4). The tree p has no node at this position.

Only one node exists here, so the shapes differ. The base case returns p is None and q is None, which is False.

The values 2 and 2 are equal. The left pair returned False and the right pair returned False, so the call returns False.

Call isSameTree on the pair (3, 3). The call waits for the answers of its two child pairs.

The values 3 and 3 are equal. The left pair returned True (both children are missing) and the right pair returned True (both children are missing), so the call returns True.

The values 1 and 1 are equal. The left pair returned False and the right pair returned True, so the call returns False.

The call on the two roots returned False, so the trees are not the same.

421now3241now3
calls waiting (p value, q value)
1/1
finished pairs
empty
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Returning True when only one of the two nodes is missing

    Mistake
    return True
    Fix
    return p is None and q is None

    What happens: A pair with a missing node ends the walk. When both nodes are missing, the two trees end at the same place, so True is correct. When only one node is missing, one tree has a node where the other has none, so the shapes differ and the answer must be False. return True reports a match in both cases. On p = [1,2] and q = [1,null,2], the pair (p.left, q.left) is (2, None) and the pair (p.right, q.right) is (None, 2). Both pairs return True, so two different trees are reported as the same.

    Example: Same Tree (LC 100), Example 2: p = [1,2], q = [1,null,2]
    Mistake: returns TrueFix: returns False
  2. 2

    Gotcha 2: Reading p.val before checking that both nodes exist

    Mistake
    if p.val != q.val:
        return False
    if not p or not q:
        return p is None and q is None
    Fix
    if not p or not q:
        return p is None and q is None

    What happens: A missing node is None, and None has no val. When one tree is shorter, a missing node reaches the first line and q.val raises an AttributeError. This also happens at every leaf, because the two missing children of a leaf make the pair (None, None). Check for a missing node first, then read val. On p = [1,2] and q = [1], the pair (p.left, q.left) is (2, None), so the call crashes instead of returning False.

    Example: p = [1,2], q = [1]
    Mistake: raises AttributeErrorFix: returns False
  3. 3

    Gotcha 3: Comparing child nodes with == instead of walking them

    Mistake
    left_same = p.left == q.left
    right_same = p.right == q.right
    Fix
    left_same = isSameTree(p.left, q.left)
    right_same = isSameTree(p.right, q.right)

    What happens: p.left == q.left asks whether both names point to the same node object. Two separate trees never share nodes, so the answer is False for every pair of real children, even when their values and shapes are equal. The walk never reaches the children's values. On p = [1,2,3] and q = [1,2,3], the root pair fails at p.left == q.left and the call returns False.

    Example: Same Tree (LC 100), Example 1: p = [1,2,3], q = [1,2,3]
    Mistake: returns FalseFix: returns True
  4. 4

    Gotcha 4: Comparing the two lists of values instead of walking the trees together

    Mistake
    return preorder(p) == preorder(q)
    Fix
    return isSameTree(p, q)

    What happens: A list of values, such as the preorder list, forgets where the missing children are. Two trees with different shapes can give the same list. For p = [1,2,2,3] and q = [1,2,2,null,3], both preorder lists are [1, 2, 3, 2], but 3 is the left child of the left 2 in p and the right child of the left 2 in q. A list comparison says True. The pair walk compares the left children of the left 2 as one pair and finds that one of them is missing, so it returns False.

    Example: p = [1,2,2,3], q = [1,2,2,null,3]
    Mistake: returns TrueFix: returns False
Not this one if…
  • Both p and q are nodes of one tree, as in Lowest Common Ancestor of a Binary Tree (LC 236): there is only one tree to walk, so use Bottom-Up DFS, where each call returns what its subtree found. See Bottom-Up DFS

Deep Dive

Why a call can trust the answers of its child pairs

isSameTree(p, q) is defined by one sentence: it returns True exactly when the subtree at p and the subtree at q have the same shape and the same values. The code only has to keep that sentence true for one call, and the sentence then holds for every call.

Two missing nodes are two empty subtrees, and two empty subtrees are the same. So return p is None and q is None gives True there. One missing node and one real node cannot be the same, because one subtree has a node where the other has none. The same line gives False. For two real nodes, the subtrees are the same exactly when the two values are equal, the left subtrees are the same and the right subtrees are the same. The two child calls answer the last two parts, so the call only compares the two values.

Take p = [1, 2, 3, 4] and q = [1, 2, 3, null, 4]. In both trees the node 4 hangs under the node 2. It is the left child in p and the right child in q. The call on the pair (2, 2) makes two calls: the pair (4, None) and the pair (None, 4). Each pair has one missing node and returns False. So the pair (2, 2) returns False, even though its two values are equal. The pair (3, 3) returns True. The call on the roots receives False and True, and returns False.

The walk makes 7 calls in all: 5 calls on pairs where at least one node exists, and 2 calls below the pair (3, 3), where both nodes are missing. The two trees have the same preorder list of values, [1, 2, 4, 3]. A comparison of the two lists would answer True. The walk answers False because it compares positions.

Which children to pair: the same side, crossed, or both ways

The card's walk pairs p.left with q.left and p.right with q.right. That is one choice. The rest of the walk is the same for every problem on this card: a base case for a pair with a missing node, two calls on child pairs, and a combine step. Only the pairing and the combine change.

Symmetric Tree (LC 101) crosses the pairs. A mirror pairs the outer nodes and the inner nodes, so the walk starts on the root's two children and calls is_mirror(a.left, b.right) and is_mirror(a.right, b.left). In the card's template this is mirror=True. For root = [1, 2, 2, null, 3, null, 3] the answer is False: the left 2 has only a right child, and the right 2 has only a right child, so the right child of the left 2 faces a missing node. Pairing the same side instead answers True for this tree.

Merge Two Binary Trees (LC 617) builds a tree. When one node of a pair is missing, the base case returns the node that exists, a or b, and that whole branch joins the result. The combine builds TreeNode(a.val + b.val, left, right). For root1 = [1, 3, 2, 5] and root2 = [2, 1, 3, null, 4, null, 7] the result is [3, 4, 5, 5, 4, null, 7]. If the base case returned None for a pair with one missing node, as Same Tree does, the result would shrink to [3, 4, 5], because every branch that exists in only one tree would be lost.

Flip Equivalent Binary Trees (LC 951) accepts either pairing at every node. The combine tries the same side first and the crossed pairs second: the same-side pairs both match, or the crossed pairs both match. The node values are unique in this problem. For that reason only one of the two pairings can match the child values, and the other fails after one comparison. The cost stays at O(min(N, M)).

Subtree of Another Tree: the walk as a check at every node

Subtree of Another Tree (LC 572) asks whether subRoot appears inside root. Run the card's walk on the pair (node, subRoot) for every node of root, and stop at the first True. The walk is the check. The outer loop is an ordinary tree walk.

The check needs the full base case. Suppose the walk returned True as soon as subRoot was used up. Then root = [3, 4, 5, 1, 2, null, null, null, null, 0] would match subRoot = [4, 1, 2], because the subtree of the node 4 starts with 4, 1, 2. But the node 2 in root has an extra child 0, so the answer is False. With the card's base case, the pair (0, None) returns False, and the extra node is seen.

The cost is O(N * M) in the worst case. The walk runs at each of the N nodes of root, and it never goes past the smaller tree, so one walk costs at most O(M), where M is the number of nodes in subRoot. Giving every distinct subtree an id lets one pass answer the question, but it needs a different idea and is not part of this card.

The cost, and where the walk stops

A call on a pair where both nodes exist makes two more calls. A call on a pair with a missing node makes none. So the number of calls is at most 2 * min(N, M) + 1. For two identical trees of 7 nodes it is exactly 15. Each call does constant work, so the time is O(min(N, M)).

The recursion goes down only while both nodes exist. The stack holds at most one frame per level of the shallower tree, so the space is O(H), where H is the height of the shallower tree.

The blueprint calls both children before it compares the values. A difference at the root therefore does not stop the walk. A version that returns False as soon as p.val != q.val stops earlier on trees that differ near the root. The worst case is the same, because two identical trees must be walked to the end.

A chain of 1000 nodes goes past Python's default limit of about 1000 nested calls. The explicit-stack version keeps the pairs on its own list. It starts with stack = [(p, q)], pops a pair, returns False on a mismatch, and pushes (a.left, b.left) and (a.right, b.right).

60-Second Pitch

"The question is about two trees, so I use a two-tree walk: one recursive call holds a node from each tree. isSameTree(p, q) pairs the children by position, p.left with q.left and p.right with q.right. If a node is missing on either side, the trees match here only when both are missing, so the base case is return p is None and q is None. Otherwise the pair matches when the two values are equal and both child pairs match. It is correct because each call can trust the two answers it receives: they are the true answers for smaller pairs of subtrees. Each position is visited once, so the time is O(min(N, M)) and the stack is as deep as the shallower tree. For Symmetric Tree, I cross the pairs. For very deep trees, I use a stack of pairs."

Common Questions

How is Two Tree Walk different from Bottom-Up DFS?

Bottom-Up DFS holds one node per call and builds the answer from the answers of its two subtrees. Two Tree Walk holds a pair of nodes, one from each tree, and builds the answer from the pair and from the answers of two child pairs. The base case also has more outcomes: two missing nodes, one missing node, and two real nodes.

Why must a pair with one missing node return False in Same Tree?

A node that exists in one tree and not in the other means the shapes differ at that position. Returning True would hide that difference. On [1, 2] and [1, null, 2] the walk would then report two different trees as the same.

How do you check that a tree is symmetric?

Start the walk on the root's left child and right child, and cross the pairs: the outer pair is (a.left, b.right) and the inner pair is (a.right, b.left). The base case and the combine step are the same as in Same Tree. Symmetric Tree (LC 101) is this walk with mirror=True.

What is the cost of Subtree of Another Tree (LC 572) with this walk?

O(N * M) in the worst case. The walk runs on (node, subRoot) for each of the N nodes of root, and one walk costs at most O(M), because it never goes past the smaller tree. The base case must return False for a pair with one missing node, so that extra nodes below a match are seen.

How do you walk two trees without recursion?

Keep a stack of pairs. Start with [(p, q)]. Pop a pair. If one node is missing and the other exists, return False. If both exist and the values differ, return False. Otherwise push (a.left, b.left) and (a.right, b.right). If the stack empties, return True.

SWE Reasoning

Spot it

  • The statement gives you two trees, p and q, or one tree that you compare with itself.
  • The question is whether they are the same, mirror images, or equal after flips, or the answer is a new tree built from both.
  • The answer for a pair of nodes depends only on the two nodes and on the answers for the pairs of their children.
  • You can say in one sentence what solve(p, q) returns for the subtrees at p and q.
  • Only one tree is walked, or the answer depends on what lies above a node: use Bottom-Up DFS or Top-Down DFS instead.

The invariant, step by step

  1. Define in one sentence what solve(p, q) returns: the answer for the subtree at p and the subtree at q.
  2. The base case handles a pair with a missing node. Two missing nodes are two empty subtrees. One missing node means the shapes differ.
  3. For two real nodes, the two child calls run first. Each pairs a child of p with its partner in q, and by the sentence in step 1 each returns a finished answer.
  4. The combine step builds this pair's answer from the two nodes and the two child answers. It does not look any deeper.
  5. So the sentence is true for this call and for every call. The call on the two roots returns the answer for the whole pair of trees.

Traps

  • Pairing the wrong children. Same Tree pairs the same side. Symmetric Tree (LC 101) crosses the pairs. Pairing the same side for a mirror question accepts [1, 2, 2, null, 3, null, 3], which is not symmetric.
  • Losing a branch that exists in one tree only. In Merge Two Binary Trees (LC 617) a pair with one missing node must return the node that exists. Returning None shrinks the result to [3, 4, 5] on the LeetCode example.
  • Stopping when the smaller tree is used up. In Subtree of Another Tree (LC 572), a base case that returns True when subRoot ends accepts a tree that has extra nodes below the match.
  • A deep chain. Two chains of 1000 nodes pass Python's default limit of about 1000 nested calls. Use an explicit stack of pairs.

Practice

Canonical LeetCode:Same Tree (LC 100)Symmetric Tree (LC 101)Merge Two Binary Trees (LC 617)Subtree of Another Tree (LC 572)Flip Equivalent Binary Trees (LC 951)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•