HARD Lab · Trees · 68

572. Subtree of Another Tree

Decide whether subRoot appears as a complete subtree of root. Walk every node of root and, at each one, run a full identical-tree check — a subtree must match shape and values all the way to the leaves.

EasyDFSTree MatchingTypeScript

PROBLEM What we're solving

Given two binary trees root and subRoot, return true if there is a node in root whose subtree is structurally identical to subRoot (same shape, same values, down to every leaf). Example: root=[3,4,5,1,2], subRoot=[4,1,2]true (the left child of 3 matches). But subRoot=[4,1,2,null,null,0] false — same head, but the shapes diverge.

KEY IDEA A subtree match = "anchor here" plus identical-tree

Insight → "is a subtree" decomposes into two independent questions. (1) Try anchoring subRoot at the current root node and ask are these two whole trees identical? (the sameTree helper). (2) If not, the answer might still be inside the left or right subtree, so recurse. The outer walk finds candidates; the inner sameTree verifies one fully.

RECIPE Walk root, verify with sameTree

  • 0 · Base cases for the walk. If subRoot is null → true (the empty tree sits inside everything). If root is null but subRoot is not → false (nothing left to match).
  • 1 · Try to anchor. Call sameTree(root, subRoot). If it returns true, this node is the answer — done.
  • 2 · Otherwise recurse. Return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot). Short-circuit on the first hit.
  • 3 · sameTree decides identity. Two trees are identical iff both nodes are null, OR both are non-null with equal values AND identical left and right subtrees. One null vs one non-null ⇒ not identical.
Classic confusion → a subtree must extend all the way down to the leaves, not just match a few top levels. sameTree is what enforces that: it keeps descending and only returns true when the entire shape lines up. A common bug is writing sameTree to stop early or to allow a partial overlap — that yields false positives.

COST Complexity & alternatives

Walk × full compare
O(m·n)
m = |root|, n = |subRoot|; sameTree may run at every node.
Serialize + substring
O(m + n)
Linear with a careful (null-delimited) preorder + KMP.

Why the naive bound is usually fine

The straightforward DFS is O(m·n) worst case, but in practice sameTreebails on the first mismatch, so it's far cheaper. The slick linear trick serializes both trees to strings with explicit null markers (so structure can't be confused) and asks whether subRoot's serialization is a substring of root's — solved in O(m+n) with KMP.

Pattern transfer → the sameTree helper is Same Tree (LC 100)verbatim, and the "walk every node and run a check" shape recurs in Symmetric Tree, Path Sum II, and Find Duplicate Subtrees (which serializes subtrees for the same reason).

RUN IT Walk root, sameTree-check on each value hit

step 0 / 2
STARTWalk every node of root. Wherever a value equals subRoot's head (4), run a full sameTree check.
preorder3041122354
State
candidate
sameTree
visitedcurrent candidatesameTree matchexhausted / no match
slowfast

TYPESCRIPT The solution, annotated

isSubtree.ts
class TreeNode {
  val: number;
  left: TreeNode | null;
  right: TreeNode | null;
  constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

function isSubtree(root: TreeNode | null, subRoot: TreeNode | null): boolean {
  if (subRoot === null) return true;   // empty tree is a subtree of anything
  if (root === null) return false;     // ran out of root, nothing to match
  if (sameTree(root, subRoot)) return true;          // anchor here?
  return isSubtree(root.left, subRoot) ||            // else search left
         isSubtree(root.right, subRoot);             // or right
}

// structural identity: same shape AND same values
function sameTree(a: TreeNode | null, b: TreeNode | null): boolean {
  if (a === null && b === null) return true;   // both empty → equal
  if (a === null || b === null) return false;  // one empty → differ
  if (a.val !== b.val) return false;           // values differ
  return sameTree(a.left, b.left) && sameTree(a.right, b.right);
}

Reading it block by block

isSubtree — base cases. A null subRoot is trivially a subtree (the empty tree is everywhere), so return true. If root ran out first while subRoot still has nodes, this branch can't match → false.
isSubtree — anchor test. Ask sameTree(root, subRoot): is the tree rooted right here identical to subRoot? If yes, we found it. This is the only place the actual matching happens.
isSubtree — recurse. No anchor here, so the match (if any) lives in a child. The || short-circuits: the moment the left subtree reports true, we stop.
sameTree — structural identity. Both null ⇒ equal. Exactly one null ⇒ different shape ⇒ not equal. Otherwise compare val, then recurse into both pairs of children. It returns true only when shape and values agree everywhere.
Complexity → O(m·n) time worst case (m = nodes in root, n = nodes in subRoot), because sameTree can run from many anchors; O(h) space for the recursion stack (h = height of root). A serialize-plus-KMP solution reaches O(m + n).

INTERVIEWFollow-ups they'll ask

  • "Can you do it in O(m + n)?" Serialize both trees as preorder strings with explicit null markers and delimiters, then test substring containment with KMP.
  • "Why the null markers in serialization?" Without them different shapes can serialize to the same string (e.g. a left-only vs right-only child), causing false matches.
  • "What if values can repeat heavily?" The naive walk still works; the anchor test simply fires more often. Linear serialization is unaffected.
  • "Subtree vs subgraph / partial match?" Clarify: this problem requires an exact, full-depth subtree, not a top-down partial overlap.
  • "Iterative version?" Replace the outer recursion with a BFS/DFS stack over root nodes, calling sameTree at each.

MNEMONIC The one-liner

"Walk root; at each node try to anchor subRoot with a full sameTree check."

TRIGGERS When you see ___ → reach for ___

"is X a subtree of Y"walk Y + sameTree at each node
match shape AND valuessameTree: both null / equal val / recurse both kids
subRoot is emptyreturn true (empty ⊆ anything)
need O(m+n)preorder serialize w/ null markers + KMP substring

SKELETON The reusable shape

skeleton.ts
function isSubtree(root, subRoot): boolean {
  if (!subRoot) return true;
  if (!root) return false;
  if (sameTree(root, subRoot)) return true;
  return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
function sameTree(a, b): boolean {
  if (!a && !b) return true;
  if (!a || !b) return false;
  return a.val === b.val && sameTree(a.left, b.left) && sameTree(a.right, b.right);
}

FLASHCARDS Tap to flip

What two helpers solve "subtree of another tree"?
An outer isSubtree walk over root, and an inner sameTree that tests full structural identity at a node.
tap to flip
1 / 6
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
What does isSubtree do at each visited node of root?
QUESTION 02
In sameTree, what happens when one node is null and the other is not?
QUESTION 03
If subRoot is null, isSubtree returns:
QUESTION 04
Worst-case time complexity of the DFS-with-sameTree approach?
QUESTION 05
Why do serialization-based O(m+n) solutions insert explicit null markers?
QUESTION 06
root=[1,1], subRoot=[1]. Is subRoot a subtree of root?
QUESTION 07
A candidate node’s value equals subRoot’s root value, but sameTree returns false. What now?