Facebook Pixel

572. Subtree of Another Tree

EasyTreeDepth-First SearchBinary TreeString MatchingHash Function
Leetcode Link

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:

  1. 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
  2. The main function that traverses through each node in root and checks if the subtree starting from that node matches subRoot

The algorithm works by:

  • First checking if the current node's subtree matches subRoot completely
  • If not, recursively checking if subRoot exists in either the left or right subtree of the current node
  • Returning true if any match is found, false if 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 (root and subRoot) 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:

  1. We need to traverse through each node in the main tree
  2. At each node, we need to check if the subtree rooted at that node matches subRoot
  3. The checking itself requires comparing entire tree structures recursively
  4. 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 root to find potential starting points
  • The recursive comparison of tree structures using the same() helper function
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Know how to verify if two patterns match exactly
  2. 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 subRoot match 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:

  1. Checking if two trees are identical (the same function)
  2. Searching through all nodes to find where to apply that check (the main isSubtree function)

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 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 p or q is None, they're only identical if both are None (checked with p 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)

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:

  1. Base case: If root is None, return False (an empty tree cannot contain a non-empty subtree)

  2. Check current position: Call same(root, subRoot) to check if the tree rooted at the current node matches subRoot

  3. 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)
  4. Return result: Use the or operator to return True if any of the three checks succeed

Algorithm Flow

The algorithm works by:

  1. Starting at the root of the main tree
  2. At each node, checking if the subtree rooted there matches subRoot completely
  3. If not, recursively checking both left and right children
  4. Propagating True up the call stack as soon as any match is found

Time and Space Complexity

  • Time Complexity: O(m × n) where m is the number of nodes in root and n is the number of nodes in subRoot. In the worst case, we check every node in root (m nodes) and for each check, we might compare all nodes in subRoot (n nodes).

  • Space Complexity: O(h) where h is the height of root, due to the recursive call stack. In the worst case (skewed tree), this could be O(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 Evaluator

Example 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.

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))
38
1/**
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}
59
1/**
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};
54
1/**
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}
51

Time and Space Complexity

Time Complexity: O(n × m)

The algorithm checks if subRoot is a subtree of root by:

  1. For each node in the main tree root (which has n nodes), we potentially call the same function
  2. The same function compares two trees for equality, which in the worst case needs to traverse all m nodes of subRoot
  3. In the worst case, we check every node in root as a potential match, and for each check, we traverse up to m nodes in subRoot
  4. Therefore, the time complexity is O(n × m)

Space Complexity: O(n)

The space complexity comes from the recursion stack:

  1. The isSubtree function recursively traverses the main tree root, which can go as deep as the height of the tree
  2. In the worst case (a skewed tree), the height equals n, so the recursion stack for isSubtree uses O(n) space
  3. The same function also uses recursion, but its stack depth is bounded by min(height of root subtree, height of subRoot), which is at most O(n)
  4. Since these recursive calls don't happen simultaneously (we finish one same call before moving to the next node in isSubtree), the maximum stack depth is O(n)
  5. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More