572. Subtree of Another Tree
Problem Description
You are given two binary trees with roots called root and subRoot. Your task is to determine if subRoot appears as a subtree anywhere within root.
A subtree is defined as a tree that consists of a node and all of its descendants. For a tree to be considered a subtree of another, it must have:
- The exact same structure
- The exact same node values at corresponding positions
The main tree root is also considered a subtree of itself.
Return true if subRoot exists as a subtree within root, and false otherwise.
For example, if you have a tree root with nodes arranged in a certain pattern, and another smaller tree subRoot, you need to check if you can find a node in root where starting from that node, the structure and values exactly match subRoot.
The solution uses a depth-first search approach with two key components:
- A helper function
same(p, q)that checks if two trees are identical by comparing their root values and recursively checking their left and right subtrees - The main function that traverses through each node in
rootand checks if the subtree starting from that node matchessubRoot
The algorithm works by:
- First checking if the current node's subtree matches
subRootcompletely - If not, recursively checking if
subRootexists in either the left or right subtree of the current node - Returning
trueif any match is found,falseif no match exists after checking all possibilities
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While not explicitly a graph problem, a binary tree is a special type of graph (specifically, a connected acyclic graph). Binary trees have nodes connected by edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly deals with binary trees. We have two binary tree structures (
rootandsubRoot) and need to check if one appears as a subtree within the other.
DFS
- Yes: Since we're working with trees and need to explore each node to check if it could be the starting point of a matching subtree, DFS is the appropriate approach.
Conclusion: The flowchart leads us directly to using DFS (Depth-First Search) for this problem. This makes sense because:
- We need to traverse through each node in the main tree
- At each node, we need to check if the subtree rooted at that node matches
subRoot - The checking itself requires comparing entire tree structures recursively
- DFS allows us to systematically explore each path in the tree and perform the necessary comparisons
The DFS pattern manifests in two ways in the solution:
- The main traversal through
rootto find potential starting points - The recursive comparison of tree structures using the
same()helper function
Intuition
When we need to check if one tree is a subtree of another, the natural approach is to think about what makes two trees identical. If we can identify when two trees are the same, then we can check every possible starting position in the main tree to see if any of them match our target subtree.
Think of it like searching for a specific pattern in a larger structure. We need to:
- Know how to verify if two patterns match exactly
- Check every possible position where the pattern could start
For the first part, two trees are identical when:
- Their root values are the same
- Their left subtrees are identical
- Their right subtrees are identical
This naturally leads to a recursive comparison function that checks these three conditions.
For the second part, since subRoot could appear anywhere within root, we need to check:
- Could
subRootmatch starting from the current root node? - If not, could it be found in the left subtree?
- If not there either, could it be in the right subtree?
This is where DFS comes in naturally - we're systematically exploring each node as a potential starting point for our match. At each node we visit, we ask "Does the subtree starting here match subRoot?" If yes, we're done. If no, we continue searching in the children.
The beauty of this approach is that it breaks down a complex problem into two simpler recursive problems:
- Checking if two trees are identical (the
samefunction) - Searching through all nodes to find where to apply that check (the main
isSubtreefunction)
Both naturally fit the DFS pattern because trees have a recursive structure - each subtree is itself a tree with the same properties as the whole.
Learn more about Tree, Depth-First Search and Binary Tree patterns.
Solution Implementation
1# Definition for a binary tree node. 2# class TreeNode: 3# def __init__(self, val=0, left=None, right=None): 4# self.val = val 5# self.left = left 6# self.right = right 7 8class Solution: 9 def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: 10 """ 11 Determines if subRoot is a subtree of root. 12 A subtree of a binary tree is a tree that consists of a node in the tree 13 and all of its descendants. 14 """ 15 16 def is_same_tree(tree1: Optional[TreeNode], tree2: Optional[TreeNode]) -> bool: 17 """ 18 Helper function to check if two trees are identical. 19 Two trees are identical if they have the same structure and node values. 20 """ 21 # Base case: if either tree is None, both must be None to be identical 22 if tree1 is None or tree2 is None: 23 return tree1 is tree2 24 25 # Check if current nodes match and recursively check left and right subtrees 26 return (tree1.val == tree2.val and 27 is_same_tree(tree1.left, tree2.left) and 28 is_same_tree(tree1.right, tree2.right)) 29 30 # Base case: if root is None, subRoot cannot be its subtree 31 if root is None: 32 return False 33 34 # Check if current tree matches subRoot, or if subRoot exists in left or right subtree 35 return (is_same_tree(root, subRoot) or 36 self.isSubtree(root.left, subRoot) or 37 self.isSubtree(root.right, subRoot)) 381/** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode() {} 8 * TreeNode(int val) { this.val = val; } 9 * TreeNode(int val, TreeNode left, TreeNode right) { 10 * this.val = val; 11 * this.left = left; 12 * this.right = right; 13 * } 14 * } 15 */ 16class Solution { 17 /** 18 * Determines if subRoot is a subtree of root. 19 * A subtree of a binary tree is a tree that consists of a node and all of its descendants. 20 * 21 * @param root The root of the main binary tree 22 * @param subRoot The root of the potential subtree 23 * @return true if subRoot is a subtree of root, false otherwise 24 */ 25 public boolean isSubtree(TreeNode root, TreeNode subRoot) { 26 // Base case: if main tree is empty, it cannot contain a subtree 27 if (root == null) { 28 return false; 29 } 30 31 // Check if current node matches the subtree OR 32 // recursively check if subtree exists in left or right branches 33 return isSameTree(root, subRoot) 34 || isSubtree(root.left, subRoot) 35 || isSubtree(root.right, subRoot); 36 } 37 38 /** 39 * Helper method to check if two trees are identical. 40 * Two trees are identical if they have the same structure and node values. 41 * 42 * @param treeOne The root of the first tree 43 * @param treeTwo The root of the second tree 44 * @return true if both trees are identical, false otherwise 45 */ 46 private boolean isSameTree(TreeNode treeOne, TreeNode treeTwo) { 47 // If either node is null, both must be null for trees to be identical 48 if (treeOne == null || treeTwo == null) { 49 return treeOne == treeTwo; 50 } 51 52 // Check if current nodes have same value AND 53 // recursively verify that left and right subtrees are also identical 54 return treeOne.val == treeTwo.val 55 && isSameTree(treeOne.left, treeTwo.left) 56 && isSameTree(treeOne.right, treeTwo.right); 57 } 58} 591/** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode() : val(0), left(nullptr), right(nullptr) {} 8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 10 * }; 11 */ 12class Solution { 13public: 14 /** 15 * Determines if subRoot is a subtree of root 16 * A subtree of a tree T is a tree consisting of a node in T and all of its descendants 17 * @param root The main tree to search in 18 * @param subRoot The tree to search for as a subtree 19 * @return true if subRoot is a subtree of root, false otherwise 20 */ 21 bool isSubtree(TreeNode* root, TreeNode* subRoot) { 22 // Base case: if root is null, subRoot cannot be its subtree 23 if (!root) { 24 return false; 25 } 26 27 // Check if current tree rooted at 'root' matches subRoot 28 // OR recursively check if subRoot is a subtree of left child 29 // OR recursively check if subRoot is a subtree of right child 30 return isSameTree(root, subRoot) || 31 isSubtree(root->left, subRoot) || 32 isSubtree(root->right, subRoot); 33 } 34 35private: 36 /** 37 * Helper function to check if two trees are identical 38 * @param tree1 First tree to compare 39 * @param tree2 Second tree to compare 40 * @return true if both trees are structurally identical with same values, false otherwise 41 */ 42 bool isSameTree(TreeNode* tree1, TreeNode* tree2) { 43 // Both nodes are null - trees are identical at this point 44 if (!tree1 || !tree2) { 45 return tree1 == tree2; 46 } 47 48 // Both nodes exist - check if values match and recursively check subtrees 49 return tree1->val == tree2->val && 50 isSameTree(tree1->left, tree2->left) && 51 isSameTree(tree1->right, tree2->right); 52 } 53}; 541/** 2 * Definition for a binary tree node. 3 * class TreeNode { 4 * val: number 5 * left: TreeNode | null 6 * right: TreeNode | null 7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { 8 * this.val = (val===undefined ? 0 : val) 9 * this.left = (left===undefined ? null : left) 10 * this.right = (right===undefined ? null : right) 11 * } 12 * } 13 */ 14 15/** 16 * Checks if two trees are identical in structure and node values 17 * @param treeOne - First tree to compare 18 * @param treeTwo - Second tree to compare 19 * @returns true if both trees are identical, false otherwise 20 */ 21function isSameTree(treeOne: TreeNode | null, treeTwo: TreeNode | null): boolean { 22 // Base case: if either tree is null, both must be null to be identical 23 if (!treeOne || !treeTwo) { 24 return treeOne === treeTwo; 25 } 26 27 // Check if current nodes have same value and recursively check left and right subtrees 28 return treeOne.val === treeTwo.val && 29 isSameTree(treeOne.left, treeTwo.left) && 30 isSameTree(treeOne.right, treeTwo.right); 31} 32 33/** 34 * Determines if subRoot is a subtree of root 35 * A subtree of a tree consists of a node and all of its descendants 36 * @param root - The main tree to search within 37 * @param subRoot - The tree to find as a subtree 38 * @returns true if subRoot is a subtree of root, false otherwise 39 */ 40function isSubtree(root: TreeNode | null, subRoot: TreeNode | null): boolean { 41 // Base case: if main tree is null, it cannot contain any subtree 42 if (!root) { 43 return false; 44 } 45 46 // Check if current tree matches subRoot, or if subRoot exists in left or right subtree 47 return isSameTree(root, subRoot) || 48 isSubtree(root.left, subRoot) || 49 isSubtree(root.right, subRoot); 50} 51Solution Approach
The solution implements a DFS approach with two key functions working together:
Helper Function: same(p, q)
This function determines if two trees are identical:
- Base case: If either
porqisNone, they're only identical if both areNone(checked withp is q) - Recursive case: Trees are identical if:
- Root values match:
p.val == q.val - Left subtrees are identical:
same(p.left, q.left) - Right subtrees are identical:
same(p.right, q.right)
- Root values match:
All three conditions must be true, so we use the and operator to combine them.
Main Function: isSubtree(root, subRoot)
This function traverses the main tree looking for a matching subtree:
-
Base case: If
rootisNone, returnFalse(an empty tree cannot contain a non-empty subtree) -
Check current position: Call
same(root, subRoot)to check if the tree rooted at the current node matchessubRoot -
Recursive search: If the current position doesn't match, recursively search:
- Left subtree:
self.isSubtree(root.left, subRoot) - Right subtree:
self.isSubtree(root.right, subRoot)
- Left subtree:
-
Return result: Use the
oroperator to returnTrueif any of the three checks succeed
Algorithm Flow
The algorithm works by:
- Starting at the root of the main tree
- At each node, checking if the subtree rooted there matches
subRootcompletely - If not, recursively checking both left and right children
- Propagating
Trueup the call stack as soon as any match is found
Time and Space Complexity
-
Time Complexity:
O(m × n)wheremis the number of nodes inrootandnis the number of nodes insubRoot. In the worst case, we check every node inroot(m nodes) and for each check, we might compare all nodes insubRoot(n nodes). -
Space Complexity:
O(h)wherehis the height ofroot, due to the recursive call stack. In the worst case (skewed tree), this could beO(m).
The elegance of this solution lies in its simplicity - by separating the "matching" logic from the "searching" logic, we create two clean, recursive functions that each handle one specific responsibility.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the solution works.
Consider these two trees:
root: subRoot: 3 4 / \ / \ 4 5 1 2 / \ 1 2
We want to check if subRoot appears as a subtree within root.
Step 1: Start at root node (3)
- Call
same(node_3, subRoot_root_4) - Compare values: 3 ≠ 4, so these trees aren't identical
- Since they don't match, continue searching
Step 2: Check left subtree of root
- Call
isSubtree(node_4, subRoot) - Now at node 4, call
same(node_4, subRoot_root_4) - Compare values: 4 = 4 ✓
- Recursively check left children:
same(node_1, subRoot_node_1)- Values match: 1 = 1 ✓
- Both have no children (null = null) ✓
- Recursively check right children:
same(node_2, subRoot_node_2)- Values match: 2 = 2 ✓
- Both have no children (null = null) ✓
- All checks pass! Return
true
The algorithm finds that the left subtree of root (starting at node 4) exactly matches subRoot, so it returns true.
Let's trace through what would happen if subRoot was NOT in the tree:
root: subRoot: 3 7 / \ / \ 4 5 1 2 / \ 1 2
Step 1: Check root (3) - doesn't match subRoot root (7) Step 2: Check left subtree (4) - doesn't match subRoot root (7) Step 3: Recursively check node 4's children:
- Node 1 doesn't match (1 ≠ 7, and node 1 has no children while subRoot has children)
- Node 2 doesn't match (2 ≠ 7, and node 2 has no children while subRoot has children) Step 4: Check right subtree (5) - doesn't match subRoot root (7) Step 5: Node 5 has no children to check
Since no position matched, return false.
The key insight is that at each node, we perform a complete tree comparison using same(). If that fails, we keep searching deeper in the tree using DFS until we either find a match or exhaust all possibilities.
Time and Space Complexity
Time Complexity: O(n × m)
The algorithm checks if subRoot is a subtree of root by:
- For each node in the main tree
root(which hasnnodes), we potentially call thesamefunction - The
samefunction compares two trees for equality, which in the worst case needs to traverse allmnodes ofsubRoot - In the worst case, we check every node in
rootas a potential match, and for each check, we traverse up tomnodes insubRoot - Therefore, the time complexity is
O(n × m)
Space Complexity: O(n)
The space complexity comes from the recursion stack:
- The
isSubtreefunction recursively traverses the main treeroot, which can go as deep as the height of the tree - In the worst case (a skewed tree), the height equals
n, so the recursion stack forisSubtreeusesO(n)space - The
samefunction also uses recursion, but its stack depth is bounded bymin(height of root subtree, height of subRoot), which is at mostO(n) - Since these recursive calls don't happen simultaneously (we finish one
samecall before moving to the next node inisSubtree), the maximum stack depth isO(n) - Therefore, the space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing Subtree with Substructure
A critical pitfall is misunderstanding what constitutes a valid subtree. A subtree must include all descendants of a node, not just a partial match.
Incorrect Understanding:
root: subRoot: 1 1 / \ \ 1 3 3 \ 3 Many think subRoot matches the left portion of root, but it doesn't! The left "1" in root has a child "3", while subRoot's "1" has no left child.
Correct Understanding: A subtree match requires the entire structure from a node downward to be identical, including where nodes are absent (None values matter!).
2. Incorrect Base Case Handling
Pitfall Code:
def is_same_tree(tree1, tree2): if not tree1 and not tree2: return True if not tree1 or not tree2: # Redundant after first check return False # ... rest of logic
Better Approach:
def is_same_tree(tree1, tree2): if tree1 is None or tree2 is None: return tree1 is tree2 # Elegant one-liner handling all None cases
3. Missing Edge Case: Empty SubRoot
The problem statement typically assumes subRoot is not None, but if it could be:
Problematic Scenario:
# If subRoot is None, should this return True or False? isSubtree(root, None)
Solution: Add explicit handling:
def isSubtree(self, root, subRoot): if subRoot is None: return True # Empty tree is subtree of any tree if root is None: return False # Non-empty subRoot can't be in empty root # ... rest of logic
4. Performance Degradation with Repeated Values
When the tree has many nodes with the same value as subRoot's root, the algorithm repeatedly performs full tree comparisons:
Worst Case Example:
root: subRoot: 1 1 / \ / 1 1 2 / \ / \ 1 1 1 1
Every node with value "1" triggers a full comparison with subRoot.
Optimization Strategy: Consider using tree serialization or hashing for large trees with many repeated values:
def isSubtree(self, root, subRoot): def serialize(node): if not node: return "#" return f",{node.val},{serialize(node.left)},{serialize(node.right)}" tree_str = serialize(root) subtree_str = serialize(subRoot) return subtree_str in tree_str
5. Stack Overflow with Deep Trees
For extremely deep trees, the recursive approach can cause stack overflow.
Solution for Production Code: Implement an iterative version using explicit stack:
def isSubtree(self, root, subRoot): def is_same_tree_iterative(p, q): stack = [(p, q)] while stack: node1, node2 = stack.pop() if not node1 and not node2: continue if not node1 or not node2 or node1.val != node2.val: return False stack.append((node1.left, node2.left)) stack.append((node1.right, node2.right)) return True if not root: return False stack = [root] while stack: node = stack.pop() if is_same_tree_iterative(node, subRoot): return True if node.left: stack.append(node.left) if node.right: stack.append(node.right) return False
Key Takeaway
The most common mistake is assuming partial structural matches count as subtrees. Remember: a subtree must be an exact copy of a node and all its descendants - no additions, no omissions, no reorderings.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!