572. Subtree of Another Tree - Explanation

Problem Link

Description

Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.

A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.

Example 1:

Input: root = [1,2,3,4,5], subRoot = [2,4,5]

Output: true

Example 2:

Input: root = [1,2,3,4,5,null,null,6], subRoot = [2,4,5]

Output: false

Constraints:

  • 1 <= The number of nodes in both trees <= 100.
  • -100 <= root.val, subRoot.val <= 100


Topics

Recommended Time & Space Complexity

You should aim for a solution as good or better than O(m * n) time and O(m + n) space, where n and m are the number of nodes in root and subRoot, respectively.


Hint 1

A subtree of a tree is a tree rooted at a specific node. We need to check whether the given subRoot is identical to any of the subtrees of root. Can you think of a recursive way to check this? Maybe you can leverage the idea of solving a problem where two trees are given, and you need to check whether they are identical in structure and values.


Hint 2

When two trees are identical, it means that every node in both trees has the same value and structure. We can use the Depth First Search (DFS) algorithm to solve the problem. How do you implement this?


Hint 3

We traverse the given root, and at each node, we check if the subtree rooted at that node is identical to the given subRoot. We use a helper function, sameTree(root1, root2), to determine whether the two trees passed to it are identical in both structure and values.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Tree Structure - Understanding node structure with left/right children and how to traverse trees
  • Depth First Search (DFS) - Used to traverse every node in the main tree and compare subtrees recursively
  • Tree Comparison - Checking if two trees are structurally identical with matching values at each node

1. Depth First Search (DFS)

Intuition

To check whether one tree is a subtree of another, we do two things:

  1. Walk through every node of the main tree (root) using DFS.
  2. At each node, check if the subtree starting here is exactly the same as subRoot.

So for every node in the big tree:

  • If its value matches subRoot's root, we compare both subtrees fully.
  • If they are identical, subRoot is a subtree.
  • Otherwise, continue searching on the left and right children.

The helper sameTree simply checks whether two trees match exactly, node-for-node.

Algorithm

  1. If subRoot is empty → return true (empty tree is always a subtree).
  2. If root is empty but subRoot is not → return false.
  3. At the current root node:
    • If sameTree(root, subRoot) is true, return true.
  4. Recursively check:
    • isSubtree(root.left, subRoot)
    • isSubtree(root.right, subRoot)
  5. Return true if either side returns true.

sameTree(root1, root2):

  1. If both nodes are null → return true.
  2. If only one is null → return false.
  3. If values differ → return false.
  4. Recursively check left children and right children.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:

    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        if not subRoot:
            return True
        if not root:
            return False

        if self.sameTree(root, subRoot):
            return True
        return (self.isSubtree(root.left, subRoot) or
               self.isSubtree(root.right, subRoot))

    def sameTree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        if not root and not subRoot:
            return True
        if root and subRoot and root.val == subRoot.val:
            return (self.sameTree(root.left, subRoot.left) and
                   self.sameTree(root.right, subRoot.right))
        return False

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(m+n)O(m + n)

Where mm is the number of nodes in subRootsubRoot and nn is the number of nodes in rootroot.


2. Serialization And Pattern Matching

Intuition

Instead of comparing trees directly, we can first turn each tree into a string and then just check whether one string is contained in the other.

  1. Serialize both root and subRoot into strings using the same traversal (for example, preorder).
  2. While serializing, we must include markers for null children (like #) and separators (like $) so different shapes don't accidentally look the same in the string.
  3. Once we have:
    • S_root = serialization of the main tree
    • S_sub = serialization of the subtree
      the problem becomes:
      "Is S_sub a substring of S_root?"

To efficiently check this, we can use a linear-time pattern matching algorithm (like Z-function or KMP) instead of naive substring search.

Algorithm

  1. Serialize a tree

    • Use preorder traversal.
    • For each node:
      • Append a separator (e.g., $) + node value.
    • For each null child, append a special marker (e.g., #$ or just #).
    • This ensures structure and values are uniquely encoded.
  2. Build strings

    • Let S_sub = serialization of subRoot.
    • Let S_root = serialization of root.
  3. Combine for pattern matching

    • Build a combined string:
      combined = S_sub + "|" + S_root
      (| is just a separator that does not appear in the serializations.)
  4. Run pattern matching

    • Compute the Z-array (or use another pattern-matching algorithm) on combined.
    • Let m = length(S_sub).
    • Scan the Z-values:
      • If any Z[i] == m, then S_sub appears completely starting at that position → subRoot is a subtree → return true.
  5. If no such position is found, return false.

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def serialize(self, root: Optional[TreeNode]) -> str:
        if root == None:
            return "$#"

        return ("$" + str(root.val) + self.serialize(root.left) + self.serialize(root.right))

    def z_function(self, s: str) -> list:
        z = [0] * len(s)
        l, r, n = 0, 0, len(s)
        for i in range(1, n):
            if i <= r:
                z[i] = min(r - i + 1, z[i - l])
            while i + z[i] < n and s[z[i]] == s[i + z[i]]:
                z[i] += 1
            if i + z[i] - 1 > r:
                l, r = i, i + z[i] - 1
        return z

    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        serialized_root = self.serialize(root)
        serialized_subRoot = self.serialize(subRoot)
        combined = serialized_subRoot + "|" + serialized_root

        z_values = self.z_function(combined)
        sub_len = len(serialized_subRoot)

        for i in range(sub_len + 1, len(combined)):
            if z_values[i] == sub_len:
                return True
        return False

Time & Space Complexity

  • Time complexity: O(m+n)O(m + n)
  • Space complexity: O(m+n)O(m + n)

Where mm is the number of nodes in subRootsubRoot and nn is the number of nodes in rootroot.


Common Pitfalls

Confusing Subtree with Substructure

A subtree must match exactly from a node down to all its leaves. A common mistake is checking only if the values match without verifying that the entire structure below also matches. If subRoot has children, those children must also exist and match in the main tree. Simply finding a node with the same value is not sufficient.

Incorrect Null Handling in Tree Comparison

When comparing two trees for equality, both trees must have null children in the same positions. A frequent error is returning true when one node is null and the other is not, or failing to check both left and right subtrees. The base case must ensure that both nodes being compared are either both null (return true) or both non-null with matching values before recursing.