topic: LeetCode Problems
LeetCode 572: Subtree of Another Tree — Recursive DFS in C++
Learn how to detect an exact binary-tree subtree by combining depth-first search with a recursive same-tree comparison.
Table of contents
Disclaimer
This article is an independent educational explanation of a programming problem originally published on LeetCode. The original problem statement, examples, constraints, trademarks, and related materials belong to LeetCode and their respective rights holders.
The real-world scenario is a hypothetical educational adaptation and does not represent a confirmed company process unless explicitly stated with a reliable source.
The solution, explanation, and code are for learning and reference. Other valid approaches may exist. Readers should verify current requirements on the original LeetCode page.
Article Summary
The Subtree of Another Tree problem asks whether one binary tree appears inside another binary tree as an exact rooted subtree.
The important word is exact. A valid match must have:
- The same node values
- The same left-child structure
- The same right-child structure
- No missing descendants
- No additional descendants
A clean solution combines two recursive operations:
- Traverse every node in the main tree with depth-first search.
- At each node, check whether the tree starting there is identical to
subRoot.
This article explains how to derive that approach, why it works, how to implement it safely in C++, and why its worst-case time complexity is O(n × m).
Original Problem and Credit
This article is based on the LeetCode problem “Subtree of Another Tree.”
- Original problem: https://leetcode.com/problems/subtree-of-another-tree/description/
- Platform: LeetCode
- Original problem, examples, and constraints credit: LeetCode
- Explanation, adaptation, analysis, and commentary: Andy
Original Problem in Simple Terms
You are given the roots of two binary trees:
root: the larger tree to searchsubRoot: the candidate tree to find
Return true when some node in root begins a subtree that is completely identical to subRoot. Otherwise, return false.
A subtree begins at one node and includes all descendants of that node. Therefore, matching only part of a branch is not enough.
The relevant constraints are:
- The
roottree contains between1and2000nodes. - The
subRoottree contains between1and1000nodes. - Each node value is between
-10^4and10^4.
Real-World Scenario
Imagine a hypothetical workflow-management platform that stores a large binary decision tree.
Each node contains an integer operation code:
- The left child represents one possible next branch.
- The right child represents another possible next branch.
A team also maintains a smaller approved workflow template. Before deploying the larger workflow, the platform must determine whether the approved template already appears inside it.
A workflow section is considered a match only when:
- Its starting operation code is the same.
- Every left branch is the same.
- Every right branch is the same.
- Neither workflow contains an additional operation that the other one does not contain.
This is exactly the subtree problem. The large workflow is root, and the approved template is subRoot.
Examples
Example 1: Exact Workflow Template Found
Input:
root = [3,4,5,1,2]
subRoot = [4,1,2]
Output:
true
The subtree beginning at node 4 has this structure:
4
/ \
1 2
That structure and all node values exactly match subRoot.
Example 2: Similar Values but Different Structure
Input:
root = [3,4,5,1,2,null,null,null,null,0]
subRoot = [4,1,2]
Output:
false
The node 4 in root appears to begin the desired structure, but the node 2 has an additional descendant, 0.
The candidate subtree is therefore not identical to subRoot. A subtree match cannot ignore extra descendants.
Mapping to the Original Problem
| Hypothetical workflow concept | Original problem concept |
|---|---|
| Complete workflow registry | root |
| Approved workflow template | subRoot |
| Operation | Tree node |
| Operation code | Node value |
| First branch | Left child |
| Second branch | Right child |
| Exact embedded workflow | Identical rooted subtree |
The ordering of the branches matters. A left child cannot be exchanged with a right child, even when the values are the same.
Solution Intuition
The problem can be separated into two smaller questions.
Question 1: Where Could the Match Begin?
A valid subtree can begin at any node in root.
Therefore, we must traverse the main tree and treat each node as a possible starting point.
A depth-first search works naturally because a binary tree is recursively defined:
- A node
- A left subtree
- A right subtree
At each node, we can check the current position first and then continue into the left and right subtrees when necessary.
Question 2: How Do We Verify a Candidate?
When a node in root is selected as a possible starting point, we must compare the entire tree rooted at that node with subRoot.
This is the Same Tree operation.
Two trees are identical when all of the following are true:
- Their current nodes are both null, or both exist.
- Their current node values are equal.
- Their left subtrees are identical.
- Their right subtrees are identical.
The null checks are essential because structure is part of the answer.
For example, these trees are not identical:
Tree A: Tree B:
4 4
/ \
1 1
They contain the same values, but the child positions are different.
Combining the Two Questions
The complete strategy is:
- Use
isSubtreeto search for a possible starting node. - Use
isSameTreeto verify an exact match at that node.
This creates a useful two-layer recursive design:
Search recursion:
Try every possible subtree root.
Comparison recursion:
Verify values and structure from one possible root.
Algorithm
- Define a helper function
isSameTree(p, q). - If either
porqis null, return whether they are both null. - If both nodes exist, require:
p->val == q->val- Their left subtrees are identical.
- Their right subtrees are identical.
- In
isSubtree(root, subRoot), returnfalseifrootis null because no candidate position remains. - Check whether the tree starting at the current
rootnode is identical tosubRoot. - If it is identical, return
trueimmediately. - Otherwise, recursively search the left subtree.
- If the left search does not find a match, recursively search the right subtree.
- Return
falseonly after every possible starting node has been rejected.
Why the Algorithm Works
The main invariant is:
Every call to
isSubtree(current, subRoot)correctly determines whethersubRootoccurs atcurrentor anywhere belowcurrent.
At a particular node, there are only three possible places where a valid match can exist:
- The subtree begins at the current node.
- The subtree exists somewhere in the current node's left subtree.
- The subtree exists somewhere in the current node's right subtree.
The algorithm checks all three possibilities.
The isSameTree helper is correct because it compares the trees node by node:
- If one node is null and the other is not, their structures differ.
- If both are null, that branch matches.
- If both exist but their values differ, the trees differ.
- If their values match, the trees are identical only when both corresponding child pairs are also identical.
Therefore, isSameTree returns true exactly when the two rooted trees have the same values and structure.
Because isSubtree tests the current node and recursively covers every node below it, it returns true exactly when an identical copy of subRoot exists inside root.
Complexity Analysis
Let:
nbe the number of nodes inroot.mbe the number of nodes insubRoot.h_rootbe the height ofroot.h_subbe the height ofsubRoot.
Time Complexity
The search may visit all n nodes in root.
At each visited node, isSameTree may compare as many as m nodes before finding a mismatch or confirming a match.
Therefore, the worst-case time complexity is:
O(n × m)
This worst case can occur when many nodes in root have values and structures similar to subRoot, causing repeated comparisons to continue deeply before failing.
The final result is not written as O(n + m) because nodes in subRoot may be examined repeatedly for many different candidate positions in root.
Space Complexity
The algorithm does not create an auxiliary collection such as a vector, stack, queue, or hash map. However, recursive function calls consume call-stack space.
The search recursion can use up to O(h_root) frames. While evaluating a candidate, isSameTree can add up to O(h_sub) comparison frames.
Therefore, the auxiliary space complexity is:
O(h_root + h_sub)
In the worst case, both trees can be completely skewed, so their heights are proportional to their node counts:
O(n + m)
For balanced trees, the recursion depth is closer to:
O(log n + log m)
C++ Solution: DFS with Exact Tree Comparison
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode* left;
* TreeNode* right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(
* int x,
* TreeNode* left,
* TreeNode* right
* ) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr || q == nullptr) {
return p == q;
}
return p->val == q->val &&
isSameTree(p->left, q->left) &&
isSameTree(p->right, q->right);
}
public:
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (root == nullptr) {
return false;
}
if (isSameTree(root, subRoot)) {
return true;
}
return isSubtree(root->left, subRoot) ||
isSubtree(root->right, subRoot);
}
};
Code Explanation
The isSameTree Helper
if (p == nullptr || q == nullptr) {
return p == q;
}
When at least one pointer is null, the two positions match only when both pointers are null.
This single condition handles three cases:
p | q | Result |
|---|---|---|
| null | null | true |
| null | node | false |
| node | null | false |
When both nodes exist, the function checks the current values and both child branches:
return p->val == q->val &&
isSameTree(p->left, q->left) &&
isSameTree(p->right, q->right);
C++ evaluates && from left to right and stops as soon as one condition is false.
Therefore:
- Different current values stop the comparison immediately.
- A mismatching left subtree prevents an unnecessary right-subtree comparison.
- The function returns
trueonly when every required comparison succeeds.
The isSubtree Search
if (root == nullptr) {
return false;
}
Reaching a null position means there are no more nodes at which a non-empty subRoot could begin.
The algorithm then tests the current node:
if (isSameTree(root, subRoot)) {
return true;
}
When the current subtree is not identical, the search continues below it:
return isSubtree(root->left, subRoot) ||
isSubtree(root->right, subRoot);
The || operator also short-circuits. If the left subtree contains a match, C++ returns true without searching the right subtree.
Walkthrough
Consider the second example:
root = [3,4,5,1,2,null,null,null,null,0]
subRoot = [4,1,2]
The main tree is:
3
/ \
4 5
/ \
1 2
/
0
The candidate tree is:
4
/ \
1 2
Step 1: Test the Main Root
Compare the subtree starting at 3 with the candidate starting at 4.
3 != 4
The comparison fails immediately.
Step 2: Search the Left Subtree
The search moves to node 4.
The current values match:
4 == 4
The left children also match:
1 == 1
Both node 1 branches are null, so that part is identical.
The right children have value 2, so their values also match:
2 == 2
However, the node 2 in the main tree has a left child 0, while the node 2 in subRoot has no left child.
The helper eventually compares:
0 versus null
Exactly one pointer is null, so isSameTree returns false.
Step 3: Continue Searching
The algorithm continues checking the remaining nodes in the main tree.
None of them begins a tree identical to subRoot, so the final answer is:
false
The extra node 0 cannot be ignored because a subtree includes all descendants of its starting node.
Common Mistakes
1. Comparing Only Node Values
Finding a node whose value equals subRoot->val does not prove that the entire subtree matches.
All corresponding descendants must also match.
2. Ignoring Null Positions
Null pointers encode tree structure.
The following situations must be treated differently:
Both pointers are null -> matching structure
Only one pointer is null -> different structure
Without these checks, trees with different shapes may incorrectly appear equal.
3. Accepting a Partial Match
A candidate is not a valid subtree when the corresponding node in root contains additional descendants.
The match must include all descendants of the selected starting node.
4. Searching Only Nodes with Different Root Values
A repeated value can appear in many positions. Even when one node with the correct value fails, another node with the same value may begin a valid match.
The traversal must continue until a complete match is found or every node has been tested.
5. Reporting O(n + m) Time
The algorithm does not compare the trees only once.
It may run an O(m) comparison at many of the n candidate nodes, producing O(n × m) time in the worst case.
6. Calling top(), Indexing, or Dereferencing Without Validation
For tree recursion, always check whether a pointer is null before reading:
node->val
node->left
node->right
The helper's null base case prevents invalid pointer access.
Key Takeaways
- Subtree matching requires exact structure and values. A partial branch or a branch with extra descendants is not a match.
- Separate searching from comparison. Use one recursive function to visit candidate roots and another to compare two trees.
- Null pointers are part of the structure. Correct base cases are essential for distinguishing different tree shapes.
- The worst-case time complexity is
O(n × m). The candidate tree may be compared repeatedly at many nodes. - Recursive space depends on tree height. It is
O(h_root + h_sub), which becomesO(n + m)for skewed trees.