LeetCode 572: Subtree of Another Tree
Have you ever tried to find a smaller picture inside a bigger picture? That's essentially what this problem is — but with trees instead of pictures. 🌳
🧩 The Problem
Given the roots of two binary trees
rootandsubRoot, returntrueif there is a subtree ofrootwith the same structure and node values ofsubRootandfalseotherwise.
A subtree of a binary tree is a tree that consists of a node in the tree and all of this node's descendants. The tree could also be considered a subtree of itself.
Examples
Example 1:
root = [3, 4, 5, 1, 2]
subRoot = [4, 1, 2]
3 4
/ \ / \
4 5 1 2
/ \
1 2
Output: true ✅
The subtree rooted at node 4 in root matches subRoot exactly.
Example 2:
root = [3, 4, 5, 1, 2, null, null, null, null, 0]
subRoot = [4, 1, 2]
3 4
/ \ / \
4 5 1 2
/ \
1 2
/
0
Output: false ❌
Even though node 4 exists in root with children 1 and 2, node 2 has an extra child 0 — so the structure doesn't match.
Constraints
- The number of nodes in the
roottree is in the range[1, 2000] - The number of nodes in the
subRoottree is in the range[1, 1000] -10⁴ <= root.val <= 10⁴-10⁴ <= subRoot.val <= 10⁴
🤔 How I Approached This
My first thought was: "I need to compare two trees — but I have to check every possible starting node in root."
The classic way to solve this is a recursive DFS — for each node in root, check if the subtree starting there matches subRoot. That works, but it can be in the worst case because you're doing a full tree comparison at each node.
Then I thought: what if I turn both trees into strings and just check if one string is inside the other?
That's the serialization approach — and it's surprisingly clean.
💡 The Key Insight: Tree → String
If we convert a tree into a string representation (a serialization), then checking "is this subtree present?" becomes a simple substring search.
Think of it like this:
- Convert
roottree →"#3 #4 #1 X X #2 X X #5 X X" - Convert
subRoottree →"#4 #1 X X #2 X X" - Now just check: is the second string inside the first one? →
sub_str in root_str
Why the # prefix matters
Without the #, we'd have a subtle bug. Consider:
- A node with value
12serializes to12 - A node with value
2serializes to2 - The string
"2"is found inside"12"— false positive! 💥
By prefixing every node value with #, we get #12 vs #2 — no ambiguity.
Why X for null?
We need null markers to preserve the structure. Without them, two trees with different shapes but the same values would look identical as strings.
💻 My Solution
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def isSubtree(self, root, subRoot):
"""
:type root: Optional[TreeNode]
:type subRoot: Optional[TreeNode]
:rtype: bool
"""
def serialize(node):
if not node:
return "X" # null marker
return "#" + str(node.val) + " " + serialize(node.left) + " " + serialize(node.right)
root_str = serialize(root)
sub_str = serialize(subRoot)
return sub_str in root_strLet's Walk Through It
For Example 1 (root = [3,4,5,1,2], subRoot = [4,1,2]):
Step 1 — Serialize root:
serialize(3)
= "#3 " + serialize(4) + " " + serialize(5)
= "#3 " + "#4 #1 X X #2 X X" + " " + "#5 X X"
= "#3 #4 #1 X X #2 X X #5 X X"
Step 2 — Serialize subRoot:
serialize(4)
= "#4 " + serialize(1) + " " + serialize(2)
= "#4 " + "#1 X X" + " " + "#2 X X"
= "#4 #1 X X #2 X X"
Step 3 — Substring check:
"#4 #1 X X #2 X X" in "#3 #4 #1 X X #2 X X #5 X X"
→ True ✅
⏱️ Complexity Analysis
| Time | Space | |
|---|---|---|
| Serialization | O(m + n) | O(m + n) |
Substring search (in) |
O(m + n)* | O(1) |
| Total | O(m + n) | O(m + n) |
Where m = number of nodes in root, n = number of nodes in subRoot.
*Python's
inoperator for strings uses an optimized algorithm (a mix of Boyer-Moore and Horspool), so the average case is very fast — often better than .
Why this beats 93% 🔥
Most people solve this with the recursive DFS approach which is in the worst case. Our serialization approach reduces it to essentially because:
- We walk each tree once to build the string
- We do one substring search
Runtime: 16ms — Beats 93.71%
Memory: 13.67 MB — Beats 39.40%
The memory is slightly higher because we store two full string representations — that's the trade-off for the speed gain.
🔄 The Classic Alternative: Recursive DFS
For completeness, here's the traditional approach that most solutions use:
class Solution(object):
def isSubtree(self, root, subRoot):
def isSameTree(p, q):
if not p and not q:
return True
if not p or not q:
return False
return (p.val == q.val and
isSameTree(p.left, q.left) and
isSameTree(p.right, q.right))
if not root:
return False
if isSameTree(root, subRoot):
return True
return (self.isSubtree(root.left, subRoot) or
self.isSubtree(root.right, subRoot))How this works:
- At every node in
root, check if the subtree starting here matchessubRootusingisSameTree - If not, recursively try the left and right children
Complexity:
- Time: — for each of the
mnodes in root, we might compare up tonnodes - Space: — where
his the height of the root tree (recursion stack)
📊 Comparison: Serialization vs Recursive DFS
| Feature | Serialization (My Approach) | Recursive DFS |
|---|---|---|
| Time | O(m + n) avg | O(m × n) worst |
| Space | O(m + n) for strings | O(h) recursion stack |
| Code Simplicity | Very clean — 6 lines | More verbose |
| Edge Cases | Handled by # prefix + X nulls |
Need careful null checks |
| Interview Impression | Shows creative thinking 💡 | Shows solid DFS fundamentals |
⚠️ Common Mistakes
- Missing the
#prefix — Without it, values like2match inside12. This is the most common bug with serialization approaches. - Forgetting null markers — Without
Xfor nulls,[1, 2]and[1, null, 2]would serialize identically even though they're different trees. - In the DFS approach: forgetting the base case — If
rootisNonebutsubRootisn't, returnFalse. If both areNone, returnTrue.
🧠 Interview Tips
- Start with DFS — If you're in an interview, start by explaining the recursive approach. It's intuitive and shows you understand tree traversal.
- Then optimize — Mention the serialization trick as an optimization. Interviewers love when you can think about the same problem in multiple ways.
- Explain the
#prefix — If you use serialization, be ready to explain why you need the delimiter. This is a common follow-up question. - Know the trade-off — Serialization is faster but uses more memory. Be ready to discuss when you'd pick one approach over the other.
- Related problems — This connects to LeetCode 100 (Same Tree), which is the helper function in the DFS approach. If you've solved that, you're halfway there!
