Start learning free

LESSON 419 OF 540 · BINARY TREES

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

    SCALA · BINARY TREES

    Subtree Of Another Tree in Scala, explained

    Problem overview

    isSubtree receives two binary trees, root and subRoot, and returns a Boolean: true when some node of root, together with all of its descendants, forms exactly the same tree as subRoot. Same means 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 a value and two fields, left and right, which are null when a child is missing.

    With root = [3,4,5,1,2] and subRoot = [4,1,2], the node 4 of root has the children 1 and 2, which is exactly subRoot, so the answer is true. If the 2 in root also had a child 0, as in [3,4,5,1,2,null,null,null,null,0], the answer would be false: a subtree takes every descendant of its node, and subRoot has no 0. For root = [1,1] and subRoot = [1] the answer is true. The root 1 has a child, so the tree under it is not subRoot, but its left child has nothing below it and matches.

    Approach: compare at every node

    The note asks for O(n · m) time, where n and m are the numbers of nodes in root and subRoot. The idea has two parts.

    1. A helper, isSameTree, answers whether two trees are identical. It walks both trees in step and stops at the first difference.
    2. isSubtree calls that helper at the top of root, and if it fails, tries again at the left child and at the right child, and so on down the tree.

    Each comparison stops after at most m nodes: it can't go deeper than subRoot does, because once subRoot runs out, the two sides differ and the answer is false. One comparison is started at each of the n nodes of root, so the total is O(n · m).

    Step by step

    The lesson writes isSubtree first, assuming the helper exists, and then the helper.

    1. Base case for the search. If root is null, return false. An empty tree has no node where subRoot could start, and subRoot always has at least one node. This check also ends the recursion when it steps below a leaf.
    2. Try the current node. If isSameTree(root, subRoot) is true, subRoot starts here, so return true.
    3. Try lower down. Otherwise return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot). Together with step 2, this tries every node of root as the starting point. The || operator skips the right side when the left side already found a match.
    4. Write the helper's signature. isSameTree takes two TreeNode parameters, p and q, and returns a Boolean.
    5. Both empty. If p == null && q == null, return true: two empty trees are the same. This is how the recursion ends when both trees run out at the same time.
    6. Only one empty. If p == null || q == null, return false. Both being empty was handled above, so exactly one tree is empty and the shapes differ.
    7. Different values. If p.value != q.value, return false. Both nodes exist now, so reading their values is safe.
    8. Compare the children. The roots match, so the trees are the same exactly when the left subtrees are the same and the right subtrees are the same: isSameTree(p.left, q.left) && isSameTree(p.right, q.right).

    The solution in Scala

    class Solution {
        def isSameTree(p: TreeNode, q: TreeNode): Boolean = {
            if (p == null && q == null) {
                return true
            }
            if (p == null || q == null) {
                return false
            }
            if (p.value != q.value) {
                return false
            }
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
        }
    
        def isSubtree(root: TreeNode, subRoot: TreeNode): Boolean = {
            if (root == null) {
                return false
            }
            if (isSameTree(root, subRoot)) {
                return true
            }
            return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot)
        }
    }
    

    Scala details worth knowing:

    • The order of the checks matters. The two null checks come before p.value is read. Reading a field of null throws a NullPointerException, so the value comparison is only safe once both nodes are known to exist.
    • == and null. p == null is a normal comparison in Scala and is safe even when p is null. Only reading a field or calling a method on null fails.
    • Short-circuit operators. && and || evaluate their right side only when needed. That is what lets a comparison stop at the first difference and the search stop at the first match.
    • Methods can call each other in any order. Inside a class, isSubtree can use isSameTree whether it is written above or below it. The explicit result types, Boolean, are required on these methods because they call themselves recursively.
    • return. The last expression of a block is its value in Scala, so return is often left out, but it works here to leave the method early, and it keeps each case on its own line.

    Complexity

    Time: O(n · m). isSubtree visits each of the n nodes of root once and starts one isSameTree call at each. A comparison visits at most m nodes of subRoot, plus the empty places just below them, which is still O(m). In most trees, mismatches are found early and the work is much smaller, but the worst case, a tall chain of equal values, reaches the bound.

    Space: O(h), where h is the height of root. The recursion needs one call frame per level, and the lesson limits the trees to 400 levels, which the JVM's call stack handles easily.

    Edge cases and common mistakes

    • Checking only the root. Comparing root with subRoot and nothing else misses every match lower down. With root = [1,2] and subRoot = [2] the answer is true, but only because the search goes down to the left child.
    • Matching values instead of whole subtrees. Finding a node with the same value as subRoot's root is not enough. The tree under it must match completely, including the places where a child is missing. That is why the helper treats null against a real node as a difference.
    • A match that is too small. With root = [1,2] and subRoot = [1] the answer is false: the root has a child, so the tree under it is not a single node, and no other node holds a 1.
    • Shapes that look alike. [1,null,2] and [1,2] hold the same values but differ in which side the 2 hangs on. Comparing left with left and right with right keeps them apart.
    • A tree is a subtree of itself. root = [5] and subRoot = [5] gives true; the check at the top of root finds it.

    Testing

    These results come from running the solution above on each input:

    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,2][2]true
    [1,2][1]false
    [1,null,2][2]true
    [1,2,3][3,4]false
    [5][5]true
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free