Start learning free

LESSON 415 OF 536 · BINARY TREES

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

    JAVA · BINARY TREES

    Subtree Of Another Tree in Java, explained

    Problem overview

    The method isSubtree receives the roots of two binary trees, root and subRoot, and returns true when subRoot is a subtree of root. A subtree here is a node of root together with all of its descendants. The match must be exact: the same shape and the same value in every place. A tree counts as a subtree of itself.

    The lesson provides the TreeNode class, with the fields val, left and right and a constructor that takes the value. A missing child is null.

    Take root = [3,4,5,1,2] and subRoot = [4,1,2]. The node 4 in root has the children 1 and 2 and nothing below them, which is exactly subRoot, so the answer is true. Add a node 0 below the 2 and the answer becomes false: the node 4 now has a descendant that subRoot lacks, and no other node of root can match.

    Approach: compare at every node

    If subRoot appears in root, it starts at some node. So the plan has two parts:

    1. a way to ask "are the trees that start at these two nodes identical?", which the helper isSameTree answers;
    2. a walk over every node of root that asks that question at each one.

    Comparing two trees is a recursion on shape: two empty trees are the same, an empty tree and a non-empty one are different, and two non-empty trees are the same when their roots hold equal values and both pairs of children are the same trees. The walk is a recursion too: subRoot is a subtree of root if it matches at root itself, or if it is a subtree of the left child, or of the right child.

    The cost is O(n · m), where n is the number of nodes in root and m the number in subRoot. The walk visits each of the n nodes once, and a comparison can never go beyond the nodes of subRoot: it stops as soon as one side runs out of nodes or the values differ. Faster solutions exist, such as turning both trees into strings and searching one inside the other, but they need a careful encoding. This lesson keeps the direct comparison.

    Step by step

    1. Stop at an empty root. An empty tree has no node where subRoot could begin, and subRoot has at least one node, so the answer is false. This is also what ends the walk after a leaf.
    2. Try the current node. If isSameTree(root, subRoot) is true, the search is over.
    3. Otherwise search lower down. Recurse into root.left and root.right with the same subRoot, joined with ||. If the left side finds it, the right side is skipped.
    4. Write isSameTree(p, q). Both parameters are TreeNodes and it returns boolean.
    5. Both null means the same. Two empty trees are identical, and this is where the comparison stops when both sides end together.
    6. Exactly one null means different. The both-null case has returned already, so if either is null here, the other one is not.
    7. Different values mean different trees. Neither node is null at this point, so reading val 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, joined with &&.

    The solution in Java

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

    Details worth a closer look:

    • The order of the checks matters. p.val on a null reference throws a NullPointerException, so the two null checks come before the value check. The first check handles both-null, which lets the second one, p == null || q == null, mean "exactly one".
    • Short-circuiting. && and || evaluate the right-hand side only when the left one doesn't already decide the result. That saves work, and it never changes the answer here, because neither call has side effects.
    • Values, not references. The code compares val, an int, with !=, which is correct for primitives. Comparing the nodes themselves with == would ask whether they are the very same object. Two separately built nodes holding 5 are different objects, so a == b is false for them, while isSameTree(a, b) is true.
    • The helper has no modifier. isSameTree is package-private, and isSubtree is the public method the tests call. The helper is an instance method, so isSubtree calls it by name without any prefix.

    Complexity

    Time: O(n · m). isSubtree reaches each of the n nodes of root at most once, and each visit starts one isSameTree call. That call visits at most m pairs of nodes, since it only moves down while both trees still have nodes, and subRoot has m of them. The worst case is a root full of values that match subRoot's for a long stretch before differing.

    Space: O(h), where h is the height of root, for the recursion. The lesson limits the trees to 400 levels, so the recursion depth stays small. A tree shaped like a linked list would make h equal to n, and a much deeper one could overflow the stack.

    Edge cases and common mistakes

    • A subtree takes every descendant. With root = [3,4,5,1,2,null,null,null,null,0] and subRoot = [4,1,2] the result is false. Checking only that the node's value and its children match would answer true wrongly. The recursion has to reach the nulls below 1 and 2 to see the extra 0.
    • Equal values on different levels. For root = [1,1] and subRoot = [1] the comparison at the top fails, because the root has a child, but the left child is a leaf holding 1, so the result is true. This is why the walk continues after a failed comparison.
    • Same values, different shape. root = [1,2] and subRoot = [2,null,3] gives false, and root = [1,null,2] with subRoot = [2,1] also gives false: a child on the other side, or in a different place, is a different tree.
    • Returning after the first failed comparison. Writing return isSameTree(root, subRoot); alone only tests the top of the tree.
    • Forgetting the empty-root check. Without it the recursion walks past the leaves and fails with a NullPointerException when it reads root.left of a missing node.

    Testing

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