Start learning free

LESSON 415 OF 536 · BINARY TREES

Subtree of Another Tree in Python: O(n·m) with Recursion

Practice O(n · m) Read the full explanation

Given the roots of two binary trees, root and subRoot, return True if subRoot is a subtree of root, and False otherwise.

A subtree of a tree is a node of that tree together with all of its descendants. subRoot is a subtree of root when some node of root and everything below it form exactly the same tree as subRoot: the same shape and the same value in every place. A tree is also a subtree of itself.

The trees below are written in level order (top to bottom, left to right), with None for a missing child.

 

Example 1:

      root        subRoot
       3             4
      / \           / \
     4   5         1   2
    / \
   1   2

Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: True
Explanation: The node 4 of root, with its children 1 and 2, is the same tree as subRoot.

Example 2:

      root        subRoot
       3             4
      / \           / \
     4   5         1   2
    / \
   1   2
      /
     0

Input: root = [3,4,5,1,2,None,None,None,None,0], subRoot = [4,1,2]
Output: False
Explanation: The node 4 of root has 1 and 2 below it, but the 2 has a child 0 that subRoot doesn't have. A subtree takes every descendant of its node, so no node of root gives the tree subRoot.

Example 3:

Input: root = [1,1], subRoot = [1]
Output: True
Explanation: The root 1 has a child, so the tree under it isn't subRoot. Its left child 1 has nothing below it: that subtree is the same as subRoot.

 

Constraints:

  • The number of nodes in root is in the range [1, 2000].
  • The number of nodes in subRoot is in the range [1, 1000].
  • Each node value is between -104 and 104.
  • Each tree is at most 400 levels deep.

Note:

Your algorithm should run in O(n · m) time, where n and m are the numbers of nodes in root and subRoot: compare subRoot with the tree under each node of root, as in Same Tree. Each comparison stops after at most m nodes.

STEP 1 OF 8
    Ctrl + Enter to check

    PYTHON · BINARY TREES

    Subtree Of Another Tree in Python, explained

    Problem overview

    isSubtree receives the roots of two binary trees, root and subRoot. It returns True when some node of root, taken together with all of its descendants, forms exactly the same tree as subRoot: the same shape and the same value in every place. A tree counts as a subtree of itself. The lesson provides the TreeNode class: each node has val, left and right, and a missing child is None.

    Take root = [3,4,5,1,2] and subRoot = [4,1,2]. The node 4 of root has the children 1 and 2 and nothing below them, which is exactly subRoot, so the answer is True. If the 2 had a child 0, the node 4 would no longer match, because a subtree takes every descendant of its node. The answer for [3,4,5,1,2,None,None,None,None,0] is False.

    Approach: try every node, compare whole trees

    The idea splits the problem in two:

    1. Decide whether two trees are identical. This is a simple recursion that walks both trees in step.
    2. Run that comparison with subRoot against the tree under every node of root. The node can be root itself, or a node somewhere in its left or right subtree, so the search recurses down both sides.

    A comparison stops as soon as the trees differ, and it can never go past the size of subRoot, because once subRoot runs out of nodes the other tree must run out at the same place or the comparison fails. So each comparison costs at most m steps, where m is the number of nodes in subRoot. With one comparison per node of root, the total is O(n · m) for n nodes in root.

    There are ways to be faster, such as serialising both trees and searching for one string inside the other, but the comparison approach is the one that needs no extra data structure, and the constraints (at most 2,000 and 1,000 nodes) are small enough for it.

    Step by step

    1. Stop on an empty tree. If root is None, there is no node where subRoot could start, and subRoot always has at least one node, so the answer is False. This is also what ends the recursion once it steps past a leaf.
    2. Test the tree that starts at root. If isSameTree says it equals subRoot, return True at once.
    3. Otherwise look lower. Call isSubtree on root.left and on root.right, joined with or. Together with step 2, every node of root gets its turn as the starting place.
    4. Write the helper isSameTree(self, p, q). It must answer whether two trees match in shape and values.
    5. Both empty means equal. If both p and q are None, return True. This ends the recursion when both trees finish at the same place.
    6. Exactly one empty means different. Since the case of two empty trees was handled already, not p or not q means one tree has a node where the other has none, so return False.
    7. Compare the values. Both nodes exist now, so it is safe to read p.val and q.val. Different values mean different trees.
    8. Recurse on both pairs of children. The roots match, so the trees are the same exactly when the left children match and the right children match. Join the two calls with and.

    The solution in Python

    class Solution:
        def isSameTree(self, p, q):
            if not p and not q:
                return True
            if not p or not q:
                return False
            if p.val != q.val:
                return False
            return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
    
        def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
            if not root:
                return False
            if self.isSameTree(root, subRoot):
                return True
            return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
    

    Python details worth a closer look:

    • not root as a None check. A TreeNode object is always truthy (it defines no __bool__ or __len__), so not root is True only for None. That is why if not p and not q reads as "both empty".
    • Short-circuiting. and and or stop evaluating as soon as the result is known. The right pair of subtrees is skipped when the left pair already differs, and the right subtree of root is skipped when the left one already contains subRoot.
    • Methods calling methods. The helper is a method, so it is called as self.isSameTree(...). Its position above isSubtree doesn't matter to Python, because the name is looked up when the call runs.
    • Recursion depth. Trees here are at most 400 levels deep, which stays under Python's default recursion limit of 1,000 calls.

    Complexity

    Time: O(n · m). isSubtree visits each of the n nodes of root once. At each node it runs isSameTree, which looks at no more than m nodes of subRoot.

    Space: O(h), where h is the height of root. The recursion stack holds one call per level, and no other data is stored. In the worst case of a one-sided tree, that is O(n).

    Edge cases and common mistakes

    • Checking only the root's value. The trees [1,2] and [1] have the same root value, but the first has an extra child, so [1] is not a subtree of it. Comparing a node must include everything below it.
    • Stopping when the first match is partial. In [1,1] with subRoot = [1], the root 1 has a child and doesn't match, but its left child is a lone 1 and does. The answer is True, because the search continues after a failed comparison.
    • Forgetting the one-sided empty case. Without the check for exactly one empty tree, the next line would read .val on None and raise AttributeError.
    • Repeated values. Many nodes may hold the same value, as in [5,None,5,None,5]. Each candidate node is compared in full, so repeated values only cost time, not correctness.
    • A tree as a subtree of itself. Equal trees return True, from the comparison at root itself.

    Testing

    Building each tree from its level-order list and calling isSubtree gives:

    • root = [3,4,5,1,2], subRoot = [4,1,2] gives True.
    • root = [3,4,5,1,2,None,None,None,None,0], subRoot = [4,1,2] gives False.
    • root = [1,1], subRoot = [1] gives True.
    • root = [1], subRoot = [2] gives False.
    • root = [1,2], subRoot = [1] gives False.
    • root = [5,None,5,None,5], subRoot = [5,None,5] gives True.
    Keep going with Python500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free