138. Subtree of Another Tree
๐ LeetCode Problem: 572. Subtree of Another Tree
๐ Difficulty: Easy
๐ท๏ธ Topics: Binary Trees, Recursion, DFS, Tree Comparison
Problem Statement
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 = [3,4,5,1,2], subRoot = [4,1,2]
Output: true
Example 2:
Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false
Constraints:
- The number of nodes in the root tree is in the range [1, 2000]
- The number of nodes in the subRoot tree is in the range [1, 1000]
- -10^4 <= root.val <= 10^4
- -10^4 <= subRoot.val <= 10^4
๐ณ Visual Understanding - Drawing the Problem!
Example 1: Subtree Exists โ
Tree (root):
3
/ \
4 5
/ \
1 2
SubTree (subRoot):
4
/ \
1 2
Is subRoot a subtree of root? YES! โ
Why?
The subtree rooted at node 4 in root
is IDENTICAL to subRoot!
Visual match:
4 โโโ Same structure
/ \ โ Same values
1 2 โโ Complete match!
Example 2: Subtree Does NOT Exist โ
Tree (root):
3
/ \
4 5
/ \
1 2
/
0
SubTree (subRoot):
4
/ \
1 2
Is subRoot a subtree of root? NO! โ
Why?
Node 4 in root has:
4
/ \
1 2
/
0 โ Extra child!
SubRoot needs:
4
/ \
1 2 โ No child here!
Not identical โ Not a subtree!
Understanding "Subtree":
Key points:
1. Must match COMPLETELY from that node down
2. Can't be partial match
3. Root itself can be a subtree of root
Valid subtree:
Original: Subtree:
1 2
/ \ /
2 3 4
/
4
Subtree starting at 2 matches!
Invalid "subtree":
Original: Not a subtree:
1 1
/ \ / \
2 3 2 3
/ / \
4 5 6
Node 3 in original has no children
But "subtree" shows 3 with children
NOT a complete match!
More Examples:
Example 3: Root equals subRoot
root: 1 subRoot: 1
/ \ / \
2 3 2 3
Result: true โ
Entire root is identical to subRoot!
Example 4: SubRoot is single node
root: 1 subRoot: 2
/ \
2 3
Result: true โ
Node 2 in root matches subRoot!
Example 5: SubRoot not found
root: 1 subRoot: 5
/ \
2 3
Result: false โ
No node 5 in root!
๐ง The AHA Moment - Reusing Problem 131!
The Key Insight:
This problem = Problem 131 (Same Tree) + DFS Search!
Problem 131: isSameTree(p, q)
"Are these two trees identical?"
Problem 138: isSubtree(root, subRoot)
"Is subRoot identical to ANY subtree in root?"
How to solve:
For EACH node in root:
Check if tree at this node == subRoot
(using isSameTree from Problem 131!)
Visual Breakdown:
Tree:
3
/ \
4 5
/ \
1 2
SubRoot:
4
/ \
1 2
Process:
1. Check if tree at 3 == subRoot?
isSameTree(3, 4) โ false (values differ)
2. Check if tree at 4 == subRoot?
isSameTree(4, 4) โ true โ FOUND!
3. No need to check further (already found)
Result: true
The Recursive Strategy:
isSubtree(root, subRoot):
1. Check if current tree == subRoot
if isSameTree(root, subRoot) โ return true
2. If not, search in left subtree
if isSubtree(root.left, subRoot) โ return true
3. If not, search in right subtree
if isSubtree(root.right, subRoot) โ return true
4. If none match
return false
It's DFS search + tree comparison!
๐ง Recursive Thinking - Building the Solution
Part 1: isSameTree (from Problem 131)
We need this helper function!
isSameTree(p, q):
if p == null and q == null:
return true
if p == null or q == null:
return false
if p.val != q.val:
return false
return isSameTree(p.left, q.left) and
isSameTree(p.right, q.right)
This checks if two trees are IDENTICAL!
Part 2: isSubtree (The Main Function)
Base Case 1: root is null
No tree, no subtree!
return false
Base Case 2: Trees are identical
if isSameTree(root, subRoot):
return true
Why? We found a match!
Recursive Case: Search in subtrees
Check left: isSubtree(root.left, subRoot)
Check right: isSubtree(root.right, subRoot)
If either returns true โ we found it!
return left OR right
The Complete Logic:
isSubtree(root, subRoot):
if root is null:
return false
// Check if current position matches
if isSameTree(root, subRoot):
return true
// Search in left and right subtrees
return isSubtree(root.left, subRoot) or
isSubtree(root.right, subRoot)
๐ฏ Solution: Recursive DFS with Helper
Implementation:
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
// Base Case 1: No tree to search in
if (root == null) {
return false;
}
// Base Case 2: Found a match at current node
// WHY? If trees are identical here, we found the subtree!
if (isSameTree(root, subRoot)) {
return true;
}
// Recursive Case: Search in left and right subtrees
// WHY? Subtree might be rooted at any node
// Use OR: need to find it in at least ONE place
return isSubtree(root.left, subRoot) ||
isSubtree(root.right, subRoot);
}
// Helper: Check if two trees are identical (Problem 131!)
private boolean isSameTree(TreeNode p, TreeNode q) {
// Both null - identical
if (p == null && q == null) {
return true;
}
// One null, one not - different
if (p == null || q == null) {
return false;
}
// Values differ - different
if (p.val != q.val) {
return false;
}
// Check subtrees recursively
return isSameTree(p.left, q.left) &&
isSameTree(p.right, q.right);
}
}
Cleaner Version:
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return false;
// Check current position or search in subtrees
return isSameTree(root, subRoot) ||
isSubtree(root.left, subRoot) ||
isSubtree(root.right, subRoot);
}
private boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val &&
isSameTree(p.left, q.left) &&
isSameTree(p.right, q.right);
}
}
๐ Detailed Dry Run with Recursion Visualization
Tree (root):
3
/ \
4 5
/ \
1 2
SubRoot:
4
/ \
1 2
Complete Recursion Flow:
CALL STACK VISUALIZATION:
Level 0: isSubtree(3, subRoot)
โ
โโ root = 3, not null โ
โโ Check: isSameTree(3, 4)?
โ โ
โ โโ 3.val โ 4.val โ return false โ
โ
โโ Not same, search left: isSubtree(4, subRoot) โโ
โ โ
โ Level 1: isSubtree(4, subRoot) โ
โ โ โ
โ โโ root = 4, not null โ โ
โ โโ Check: isSameTree(4, 4)? โ
โ โ โ โ
โ โ โโ 4.val == 4.val โ โ
โ โ โ โ
โ โ โโ Check left: isSameTree(1, 1) โ
โ โ โ โ โ
โ โ โ โโ 1.val == 1.val โ โ
โ โ โ โโ Both leaves โ
โ โ โ โโ return true โ โ
โ โ โ โ
โ โ โโ Check right: isSameTree(2, 2) โ
โ โ โ โ โ
โ โ โ โโ 2.val == 2.val โ โ
โ โ โ โโ Both leaves โ
โ โ โ โโ return true โ โ
โ โ โ โ
โ โ โโ true && true โ return true โ โ
โ โ โ
โ โโ Trees match! return true โ โ
โ โ
โโ Left returned true โ return true โ โ
โ No need to check right (OR short-circuit) โ
โ โ
โโ FINAL: return true โ โ
Result: true (found subtree at node 4)
What Each Call Does:
isSubtree(3, subRoot):
"Is subRoot a subtree starting at 3?"
Check: Is tree at 3 same as subRoot? No
Search left (4): Found it! โ
Return: true
isSubtree(4, subRoot):
"Is subRoot a subtree starting at 4?"
Check: Is tree at 4 same as subRoot? Yes! โ
Return: true (no need to search further)
isSameTree calls:
- Compare nodes recursively
- Check values and structure
- Return true only if identical
๐ Dry Run - Example 2 (Not Found)
Tree (root):
3
/ \
4 5
/ \
1 2
/
0
SubRoot:
4
/ \
1 2
Recursion Flow:
Level 0: isSubtree(3, subRoot)
โ
โโ Check: isSameTree(3, 4)? โ false โ
โ
โโ Search left: isSubtree(4, subRoot) โโโโโโโโโโ
โ โ
โ Level 1: isSubtree(4, subRoot) โ
โ โ โ
โ โโ Check: isSameTree(4, 4)? โ
โ โ โ โ
โ โ โโ 4.val == 4.val โ โ
โ โ โ โ
โ โ โโ Check left: isSameTree(1, 1) โ
โ โ โ โโ true โ โ
โ โ โ โ
โ โ โโ Check right: isSameTree(2, 2) โ
โ โ โ โ โ
โ โ โ โโ 2.val == 2.val โ โ
โ โ โ โ โ
โ โ โ โโ Left: isSameTree(0, null) โ
โ โ โ โ โโ One null โ false โ โ
โ โ โ โ โ
โ โ โ โโ return false โ โ
โ โ โ โ
โ โ โโ true && false โ return false โ โ
โ โ โ
โ โโ Trees DON'T match โ continue search โ
โ โ โ
โ โโ Search left: isSubtree(1, subRoot) โ
โ โ โโ isSameTree(1, 4)? โ false โ โ
โ โ No children โ return false โ โ
โ โ โ
โ โโ Search right: isSubtree(2, subRoot) โ
โ โ โโ isSameTree(2, 4)? โ false โ โ
โ โ Continue searching... โ
โ โ All return false โ โ
โ โ โ
โ โโ return false โ โ
โ โ
โโ Left returned false โ
โ โ
โโ Search right: isSubtree(5, subRoot) โโโโโโโโโ
โ โ โ
โ โโ isSameTree(5, 4)? โ false โ โ
โ 5 is leaf โ return false โ โ
โ โ
โโ false || false โ return false โ โ
FINAL: false (subtree not found)
๐ Complexity Analysis
Time Complexity: O(m ร n)
Where:
m = number of nodes in root
n = number of nodes in subRoot
Why O(m ร n)?
- We visit each node in root: O(m)
- At each node, we call isSameTree: O(n) worst case
- Total: O(m ร n)
Detailed breakdown:
isSubtree: O(m) - visits all nodes in root
isSameTree: O(n) - compares all nodes in subRoot
For each of m nodes in root:
Potentially compare all n nodes in subRoot
Worst case: m ร n comparisons
Example:
root has 100 nodes
subRoot has 10 nodes
Worst case: 100 ร 10 = 1000 operations
Space Complexity: O(hโ + hโ)
Where:
hโ = height of root tree
hโ = height of subRoot tree
Why?
- isSubtree recursion: O(hโ) stack depth
- isSameTree recursion: O(hโ) stack depth
- Can be nested: hโ + hโ total
Best case (balanced trees): O(log m + log n)
Worst case (skewed trees): O(m + n)
Note: Both recursions can be active simultaneously
when isSameTree is called from deep in isSubtree
โ ๏ธ Common Mistakes
Mistake 1: Forgetting the Helper Function
// โ WRONG - Trying to solve without isSameTree
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return false;
// How to check if they're the same?
// Need to compare entire subtrees!
// This is Problem 131 - need that solution!
if (root.val == subRoot.val) { // Not enough!
// What about children? Need full comparison!
}
}
// โ CORRECT - Use helper function
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return false;
return isSameTree(root, subRoot) || // Use helper!
isSubtree(root.left, subRoot) ||
isSubtree(root.right, subRoot);
}
Mistake 2: Only Checking Root Values
// โ WRONG - Only compares root values
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return false;
if (root.val == subRoot.val) { // Not sufficient!
return true; // Missing: check entire subtree!
}
return isSubtree(root.left, subRoot) ||
isSubtree(root.right, subRoot);
}
// Tree: 1 SubRoot: 1
// / \ / \
// 2 3 4 5
// Would return true, but subtrees are different!
// โ CORRECT - Check entire trees
if (isSameTree(root, subRoot)) { // Full comparison!
return true;
}
Mistake 3: Wrong Base Case Handling
// โ WRONG - Returns true for null root
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return true; // WRONG!
// ...
}
// Null root has no subtrees!
// Should return false
// โ CORRECT - Null root means no subtree possible
if (root == null) return false;
Mistake 4: Not Using OR Logic
// โ WRONG - Uses AND instead of OR
return isSameTree(root, subRoot) &&
isSubtree(root.left, subRoot) &&
isSubtree(root.right, subRoot);
// This requires subtree to be EVERYWHERE!
// We only need it to exist SOMEWHERE!
// โ CORRECT - Use OR
return isSameTree(root, subRoot) ||
isSubtree(root.left, subRoot) ||
isSubtree(root.right, subRoot);
Mistake 5: Comparing References Instead of Values
// โ WRONG - Compares object references
if (root == subRoot) { // Memory address comparison!
return true;
}
// Even if values are same, objects are different!
// โ CORRECT - Compare structure and values
if (isSameTree(root, subRoot)) { // Deep comparison!
return true;
}
๐ฏ Pattern Recognition - Composing Solutions
Core Pattern: Problem Composition
Problem Composition = Combining simpler solutions
This problem =
Problem 131 (Same Tree) + DFS search
Template:
function complexProblem(params):
// Use solution from simpler problem
if (simpleProblem(params)):
return result
// Search/recurse
explore other possibilities
This is a POWERFUL technique!
Related Problems:
1. Same Tree (Problem 131) - The Building Block
Base problem: Are two trees identical?
Used as helper in:
- Subtree of Another Tree (this!)
- Symmetric Tree (with mirror twist)
- Many tree comparison problems
2. Lowest Common Ancestor
Uses tree search + comparison
- Search for nodes
- Compare subtrees
- Similar DFS pattern
3. Merge Two Binary Trees
Uses tree comparison + transformation
- Compare structures
- Combine values
- Build new tree
4. Maximum Depth (Problem 133) as Helper
Many problems use depth calculation:
- Balanced Binary Tree
- Diameter calculation
- Path problems
Composing solutions!
When to Use This Pattern:
โ Problem can be decomposed into subproblems
โ You recognize a familiar pattern
โ Can reuse previous solutions
โ Combining search + comparison
โ Need to check multiple positions
๐ Quick Revision Notes
๐ฏ Core Concept
Check if subRoot is identical to any subtree in root. Use two functions: (1) isSameTree from Problem 131 - checks if two trees are identical, (2) isSubtree - DFS search through root, checking each node with isSameTree. At each node: check if match OR search in left OR search in right. Use OR logic (only need one match).
โก Quick Implementation
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
public static TreeNode buildTree(Integer[] arr) {
if (arr == null || arr.length == 0 || arr[0] == null) {
return null;
}
TreeNode root = new TreeNode(arr[0]);
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while (!queue.isEmpty() && i < arr.length) {
TreeNode current = queue.poll();
// Process left child
if (i < arr.length) {
if (arr[i] != null) {
current.left = new TreeNode(arr[i]);
queue.offer(current.left);
}
i++;
}
// Process right child
if (i < arr.length) {
if (arr[i] != null) {
current.right = new TreeNode(arr[i]);
queue.offer(current.right);
}
i++;
}
}
return root;
}
public static void print(TreeNode root) {
if (root == null) {
System.out.println("[]");
return;
}
List<String> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node != null) {
result.add(String.valueOf(node.val));
queue.offer(node.left);
queue.offer(node.right);
} else {
result.add("null");
}
}
while (!result.isEmpty() && result.get(result.size() - 1).equals("null")) {
result.remove(result.size() - 1);
}
System.out.println(result);
}
}
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
// both root and subroot reached leaf nodes. Equal. Return true.
if(root == null && subRoot == null) {
return true;
}
// One of reached early. Not equal. Return false.
if(root == null || subRoot == null) {
return false;
}
// check for equality at current nodes
// Take child of the current node and check equality of them (left and right child) to child tree (sameTree problem).
return isSameTree(root, subRoot) || isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
private boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) {
return true;
}
if(p == null || q == null) {
return false;
}
return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.isSubtree(TreeNode.buildTree(new Integer[] {3,4,5,1,2}),
TreeNode.buildTree(new Integer[] {4,1,2})));
System.out.println(s.isSubtree(TreeNode.buildTree(new Integer[] {3,4,5,1,2,null,null,null,null,0}),
TreeNode.buildTree(new Integer[] {4,1,2})));
System.out.println(s.isSubtree(TreeNode.buildTree(new Integer[] {1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,2}),
TreeNode.buildTree(new Integer[] {1,null,1,null,1,null,1,null,1,null,1,2})));
}
}
๐ Key Insights
Problem Composition:
This problem = Problem 131 + DFS search
Problem 131: isSameTree(p, q)
"Are these trees identical?"
Problem 138: isSubtree(root, subRoot)
"Is subRoot identical to ANY subtree in root?"
Solution: For each node in root,
check isSameTree(node, subRoot)
Reusing solutions is POWERFUL! ๐
Two-Level Recursion:
Level 1: isSubtree recursion
- Traverses through root tree
- Tries each node as potential match
- O(m) nodes visited
Level 2: isSameTree recursion
- At each node, compares entire subtrees
- Checks structure and values
- O(n) comparison per node
Total: O(m ร n) time complexity
OR Logic is Critical:
Why OR (||)?
We need subtree to exist SOMEWHERE:
- At current position? OR
- In left subtree? OR
- In right subtree? OR
Only ONE match needed โ use OR!
Don't use AND:
Would require subtree everywhere!
๐ช Memory Aid
"Same Tree + Search = Subtree!"
"Check here OR left OR right!"
"Reuse Problem 131 solution!"
"Compose, don't reinvent!" โจ
โ ๏ธ Don't Forget
- Use isSameTree helper (from Problem 131!)
- Check null root (no subtrees possible)
- Use OR logic (||, not &&)
- Check entire trees (not just root values)
- Two-level recursion (search + compare)
- Short-circuit with OR (stop when found)
๐ The Composition Pattern
// Generic problem composition
boolean complexProblem(Tree data) {
// Base cases
if (baseCondition) return baseResult;
// Use simpler problem solution
if (simpleProblem(data)) {
return true;
}
// Search/explore other options
return complexProblem(data.option1) ||
complexProblem(data.option2);
}
For this problem:
simpleProblem = isSameTree (Problem 131)
complexProblem = isSubtree (search with isSameTree)
๐ Congratulations!
You've mastered problem composition!
What You Learned:
โ
Reusing solutions - Building on Problem 131
โ
Two-level recursion - Search + comparison
โ
OR logic - Finding at least one match
โ
Problem decomposition - Breaking complex into simple
โ
Helper functions - Modular solutions
โ
DFS search pattern - Checking all positions
The Power of Composition:
Instead of solving from scratch:
โ "How do I solve this new problem?"
Recognize patterns:
โ "I've seen part of this before!"
โ "I can reuse Problem 131!"
โ "Just add a search wrapper!"
Benefits:
โ Faster development
โ Less error-prone
โ Easier to understand
โ Modular and testable
This is how senior engineers think! ๐ฏ
Pattern Connection:
Problem 131: Same Tree
Base building block
Problem 132: Symmetric Tree
Uses mirror comparison (modified 131)
Problem 138: Subtree of Another Tree
Uses exact Same Tree (reused 131)
Pattern: Build solutions by COMPOSING simpler ones!
Next Steps:
Problems that use composition: - Lowest Common Ancestor (uses tree search + comparison) - Serialize/Deserialize (uses tree traversal patterns) - Path problems (combine depth + validation)
You're thinking like an expert! ๐ณ๐งโจ