Start learning free

LESSON 415 OF 536 · BINARY TREES

Subtree of Another Tree in JavaScript: O(n · m) 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 null 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,null,null,null,null,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

    JAVASCRIPT · BINARY TREES

    Subtree Of Another Tree in JavaScript, explained

    Problem overview

    isSubtree(root, subRoot) receives the roots of two binary trees and returns true when some node of root, together with all of its descendants, forms exactly the tree subRoot: the same shape and the same value in every place. A tree counts as a subtree of itself. The lesson provides the TreeNode function, whose nodes have val, left and right, with null for a missing child.

    In the first example, root = [3,4,5,1,2] and subRoot = [4,1,2]. The node 4 of root has the children 1 and 2 and nothing else below, so it matches and the answer is true. In the second example, root = [3,4,5,1,2,null,null,null,null,0]: now the 2 has a child 0, which subRoot doesn't have, so no node matches and the answer is false. Taking only part of a node's descendants is not allowed.

    Approach: try every node, compare the trees

    There are two separate questions here. First, are two given trees identical? Second, which node of root should be compared with subRoot? The answer to the second is "every one of them", so the solution asks the first question at each node.

    Comparing two trees is a simple recursion: both empty means the same, only one empty means different, different values mean different, and otherwise both pairs of children must be the same. A comparison stops as soon as it finds a difference, and it never looks at more than m nodes, where m is the size of subRoot, because it can't go deeper than subRoot does without hitting a difference. It is started once per node of root, so the total is O(n · m), which is the budget the lesson sets.

    Step by step

    1. Base case of the search. If root is null, return false: an empty tree has no node where subRoot could start, and subRoot has at least one node. This also ends the recursion when it steps past a leaf.
    2. Compare at this node. If isSameTree(root, subRoot) is true, return true right away.
    3. Look lower down. Otherwise subRoot may start in the left subtree or in the right one, so return the two recursive isSubtree calls joined with ||. Together with the check above, every node of root gets tried.
    4. Write the helper isSameTree(p, q).
    5. Both empty: if !p && !q, return true. Two empty trees are the same tree, and this is where the recursion ends when both sides run past a leaf together.
    6. Only one empty: if !p || !q, return false. Both being empty has already returned, so exactly one is missing and the shapes differ.
    7. Different values: if p.val != q.val, return false. Both nodes exist here, so reading val is safe.
    8. Recurse on both pairs of children and join the results with &&. The roots match, so the trees are the same exactly when the left subtrees are the same and the right subtrees are the same.

    The solution in JavaScript

    class Solution {
        isSameTree(p, q) {
            if (!p && !q) {
                return true;
            }
            if (!p || !q) {
                return false;
            }
            if (p.val != q.val) {
                return false;
            }
            return this.isSameTree(p.left, q.left) && this.isSameTree(p.right, q.right);
        }
    
        isSubtree(root, subRoot) {
            if (!root) {
                return false;
            }
            if (this.isSameTree(root, subRoot)) {
                return true;
            }
            return this.isSubtree(root.left, subRoot) || this.isSubtree(root.right, subRoot);
        }
    }
    

    A few details are worth noticing:

    • Methods call each other through this. Inside a class, isSubtree reaches the helper as this.isSameTree(...). A bare isSameTree(...) would look for a function in the surrounding scope and fail.
    • !p tests for null. A node is an object, so it is truthy, while null is falsy. Here the only falsy values that can appear are null.
    • Short-circuiting. && skips the right pair when the left pair already differs, and || skips the right subtree of root when the left one already contains subRoot.
    • != works on numbers here, but !== is the safer habit in your own code, because != converts types before comparing.

    Complexity

    Time: O(n · m). isSubtree visits each of the n nodes of root at most once, and at each one isSameTree compares at most m node pairs.

    Space: O(h), where h is the larger depth involved. The recursion depth is bounded by the depth of root for the search, plus at most the depth of subRoot for a comparison. The lesson limits trees to 400 levels, which is well within JavaScript's call stack.

    Edge cases and common mistakes

    • Matching only the root's value. For root = [1,1] and subRoot = [1], the top node 1 has a child, so the tree under it is not subRoot. The answer is still true, because the left child 1 has nothing below it. Checking values without checking that both sides end at the same place gives wrong answers.
    • Ignoring extra descendants. A node that matches subRoot at the top but has more nodes below is not a match. This is why isSameTree must compare the null children too.
    • Forgetting the empty-tree check in isSubtree. Without it, the recursion runs past a leaf and reads .left of null, which throws a TypeError.
    • Swapped order of the two checks in isSameTree. Reading p.val before testing for null throws the same error.
    • Same shape, different values. A tree that is a mirror image or has the right shape with other numbers must not match.

    Testing

    With trees written in level order, the solution returns:

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