572. Subtree of Another Tree - Explanation
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: trueExample 2:
Input: root = [1,2,3,4,5,null,null,6], subRoot = [2,4,5]
Output: falseConstraints:
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.
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:
- Walk through every node of the main tree (
root) using DFS. - 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,
subRootis 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
- If
subRootis empty → returntrue(empty tree is always a subtree). - If
rootis empty butsubRootis not → returnfalse. - At the current
rootnode:- If
sameTree(root, subRoot)istrue, returntrue.
- If
- Recursively check:
isSubtree(root.left, subRoot)isSubtree(root.right, subRoot)
- Return
trueif either side returnstrue.
sameTree(root1, root2):
- If both nodes are
null→ returntrue. - If only one is
null→ returnfalse. - If values differ → return
false. - 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 FalseTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of nodes in and is the number of nodes in .
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.
- Serialize both
rootandsubRootinto strings using the same traversal (for example, preorder). - While serializing, we must include markers for
nullchildren (like#) and separators (like$) so different shapes don't accidentally look the same in the string. - Once we have:
S_root= serialization of the main treeS_sub= serialization of the subtree
the problem becomes:
"IsS_suba substring ofS_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
Serialize a tree
- Use preorder traversal.
- For each node:
- Append a separator (e.g.,
$) + node value.
- Append a separator (e.g.,
- For each
nullchild, append a special marker (e.g.,#$or just#). - This ensures structure and values are uniquely encoded.
Build strings
- Let
S_sub= serialization ofsubRoot. - Let
S_root= serialization ofroot.
- Let
Combine for pattern matching
- Build a combined string:
combined = S_sub + "|" + S_root
(|is just a separator that does not appear in the serializations.)
- Build a combined string:
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, thenS_subappears completely starting at that position →subRootis a subtree → returntrue.
- If any
- Compute the Z-array (or use another pattern-matching algorithm) on
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 FalseTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of nodes in and is the number of nodes in .
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.
Sign in to join the discussion