Start learning free

LESSON 419 OF 540 · BINARY TREES

Subtree of Another Tree in TypeScript: 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

    TYPESCRIPT · BINARY TREES

    Subtree Of Another Tree in TypeScript, explained

    Problem overview

    isSubtree receives the roots of two binary trees, root and subRoot, and returns true when subRoot appears inside root. "Appears" has a strict meaning here: some node of root, taken together with all of its descendants, must have exactly the same shape and the same values as subRoot. A tree counts as a subtree of itself.

    With root = [3,4,5,1,2] and subRoot = [4,1,2], the node 4 and its children 1 and 2 match subRoot, so the result is true. If the 2 also had a child 0, the result would be false: a subtree takes every descendant of its node, so no node of root would give exactly [4,1,2].

    The lesson provides the TreeNode class, with val, left and right fields. An absent child is null.

    Approach: compare at every node

    The question splits into two smaller ones. First, are two trees identical? Second, which node of root should be compared with subRoot? We don't know where subRoot might start, so we try all of them: the root itself, then everything in the left subtree, then everything in the right subtree.

    The identical-trees check is a classic recursion: two trees are the same when their roots hold the same value and their left subtrees and right subtrees are 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. It is started at each of the n nodes of root, so the total work is O(n · m). That matches the bound in the statement, and with at most 2000 and 1000 nodes it is fast enough.

    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 check also ends the recursion when the search steps past a leaf.
    2. Try the current node. If this.isSameTree(root, subRoot) is true, return true right away.
    3. Try lower nodes. Otherwise subRoot can only start somewhere below, so return this.isSubtree(root.left, subRoot) || this.isSubtree(root.right, subRoot). The || skips the right side when the left side has already found a match.
    4. Write the helper. isSameTree(p, q) takes two TreeNode | null values and returns a boolean.
    5. Both empty. If !p && !q, the trees are equal, so return true. This is also where the recursion stops when both sides run out at the same moment.
    6. Only one empty. If !p || !q, exactly one of them is empty, because the previous check handled "both". The shapes differ, so return false.
    7. Different values. If p.val != q.val, return false. Both nodes exist at this point, so reading val is safe.
    8. Recurse. The roots match, so the trees are the same exactly when the left pair and the right pair of subtrees are: this.isSameTree(p.left, q.left) && this.isSameTree(p.right, q.right).

    The solution in TypeScript

    class Solution {
        isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
            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: TreeNode | null, subRoot: TreeNode | null): boolean {
            if (!root) {
                return false;
            }
            if (this.isSameTree(root, subRoot)) {
                return true;
            }
            return this.isSubtree(root.left, subRoot) || this.isSubtree(root.right, subRoot);
        }
    }
    

    TypeScript details

    • TreeNode | null is a union type. After if (!p || !q) return false; the compiler's control-flow analysis knows neither is null, which is why p.val and q.val are allowed on the next line. These annotations are erased when the code compiles to JavaScript; the null checks themselves are what protect you at run time.
    • !p is true for null. A TreeNode object is always truthy, even when its val is 0, so testing the node rather than its value is safe.
    • Methods call each other through this. Writing isSameTree(...) without this. would look for a function outside the class and fail.
    • != works here because both sides are numbers. In TypeScript code, !== is the usual habit.

    Complexity

    Time: O(n · m). isSubtree visits each of the n nodes of root once, and each visit calls isSameTree, which compares at most m node pairs because it ends when the smaller tree runs out.

    Space: O(h) for the recursion stack, where h is the height of root, plus up to the height of subRoot for the comparison running on top of it. The statement limits depth to 400 levels, which the call stack handles. For a very deep tree the recursion could overflow the stack.

    Edge cases and common mistakes

    • Matching only the values. Finding a node whose value equals subRoot.val is not enough. The whole tree below it must match, so a node with an extra descendant is a different tree.
    • Stopping when subRoot runs out. A version of the comparison that returns true once subRoot is exhausted would accept a node that still has children. In this solution, "one side empty, the other not" returns false.
    • Forgetting that a tree is a subtree of itself. Step 2 compares at root before going down, which covers that case.
    • Checking only the root. Without the two recursive calls in isSubtree, only subRoot equal to the whole tree would be found.
    • Repeated values. With root = [1,1] and subRoot = [1], the root 1 fails because it has a child, but its left child is a leaf with value 1 and matches. Trying every node is what makes this work.

    Testing

    Running the solution on a few trees, written in level order, gives:

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