Start learning free

LESSON 419 OF 540 · BINARY TREES

Subtree of Another Tree in Go: Recursion with isSameTree

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 nil 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,nil,nil,nil,nil,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

    GO · BINARY TREES

    Subtree Of Another Tree in Go, explained

    Problem overview

    The function isSubtree receives the roots of two binary trees, root and subRoot, and returns 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 struct, with the fields Val, Left and Right.

    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 below them, which is exactly subRoot, so the answer is true. In the second example, root has an extra node 0 below the 2. The node 4 of root now owns a bigger tree than subRoot, and no other node matches either, so the answer is false. A subtree always takes every descendant of its top node: you can't cut it off early.

    In the third example, root = [1,1] and subRoot = [1]. The root 1 has a child, so the tree under it is not subRoot. Its left child 1 has nothing below it, and that tree equals subRoot, so the answer is true.

    Approach: compare at every node

    The idea splits the problem in two:

    1. Decide whether two trees are identical. This is a small recursive function, isSameTree.
    2. Try that comparison with subRoot against the tree under every node of root.

    The statement sets a budget of O(n · m) time, where n is the number of nodes in root and m the number in subRoot. That matches the plan: there are n places to start, and each comparison looks at no more than m nodes, because it stops as soon as one tree runs out of nodes or two values differ. Faster methods exist, such as turning both trees into strings and searching for one inside the other, but this lesson uses the comparison, which needs no extra memory beyond the recursion.

    Step by step

    1. Stop at an empty tree. If root is nil, there is no node where subRoot could start, and subRoot always has at least one node. Return false. This also ends the recursion when it steps below a leaf.
    2. Check the tree that starts here. If isSameTree(root, subRoot) is true, a match has been found, so return true.
    3. Look lower down. Otherwise subRoot may start in the left or the right part, so return isSubtree(root.Left, subRoot) || isSubtree(root.Right, subRoot). Each call repeats the same three steps one level lower, so every node of root gets its turn as the starting point. The || operator skips the right call when the left one already returned true.
    4. Declare the helper. isSameTree takes two *TreeNode values, p and q, and returns a bool. It sits below isSubtree, and Go allows calling a function that is declared later in the file.
    5. Both empty means the same. If p == nil && q == nil, return true. Two empty trees are equal, and this is how the recursion ends when both sides run out of nodes together.
    6. Exactly one empty means different. The first check already handled "both empty", so if p == nil || q == nil now holds, one tree has a node where the other has none. The shapes differ, so return false.
    7. Compare the values. Both nodes exist at this point, so reading p.Val and q.Val is safe. If they differ, return false.
    8. Compare the children. The roots match, so the trees are equal exactly when the left subtrees are equal and the right subtrees are equal: isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right).

    The solution in Go

    func isSubtree(root *TreeNode, subRoot *TreeNode) bool {
    	if root == nil {
    		return false
    	}
    	if isSameTree(root, subRoot) {
    		return true
    	}
    	return isSubtree(root.Left, subRoot) || isSubtree(root.Right, subRoot)
    }
    
    func isSameTree(p *TreeNode, q *TreeNode) bool {
    	if p == nil && q == nil {
    		return true
    	}
    	if p == nil || q == nil {
    		return false
    	}
    	if p.Val != q.Val {
    		return false
    	}
    	return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right)
    }
    

    The Go details worth knowing:

    • Pointers and nil. A child that is missing is a nil pointer, so p == nil is the way to ask "is there a node here?". Reading p.Val on a nil pointer would panic, which is why the nil checks come first and in that order.
    • Short-circuit operators. || and && evaluate their right side only when needed. In isSameTree, a mismatch on the left stops the work before the right subtrees are touched.
    • Comparing structure, not pointers. p == q would ask whether both variables point to the same node in memory. Two separately built trees with equal contents are different pointers, so the function compares values and recurses instead.
    • Declaration order. Package-level functions can call each other regardless of where they appear in the file, so isSubtree may use isSameTree before it is written.

    Complexity

    Time: O(n · m). isSubtree reaches each of the n nodes of root at most once. At each node, isSameTree visits at most as many node pairs as subRoot has nodes, since every recursive step moves down in q and the recursion ends when q runs out. The worst case is a tree where many nodes look like the beginning of subRoot, for example a long chain of 1s compared against a shorter chain of 1s with a different last value.

    Space: O(h), where h is the height of root. The only extra memory is the recursion stack, whose depth is at most the height of root plus the height of subRoot. The lesson's trees are at most 400 levels deep.

    Edge cases and common mistakes

    • Whole tree equals the subtree. root = [1] and subRoot = [1] returns true, because isSameTree succeeds at the very first node.
    • Same value, different shape. With root = [1,2,3] and subRoot = [3], the answer is true, since the node 3 is a leaf. With root = [1,2] and subRoot = [2,3] it is false: the node 2 has no child 3.
    • Matching only the top part. A comparison that stops when subRoot runs out of nodes would accept a bigger tree whose upper part looks like subRoot. That is why isSameTree treats "one side is nil, the other is not" as a mismatch. The second example in the statement tests this.
    • Left versus right. A tree with a left child 2 is different from a tree with a right child 2. Comparing Left with Left and Right with Right keeps the position of every node part of the comparison.
    • Forgetting the empty-tree base case. Without root == nil at the top of isSubtree, the recursion would eventually call isSameTree(nil, subRoot) and then try to read root.Left on a nil pointer.
    • Comparing the wrong things. Comparing only the values, for example by collecting both trees into slices without marking missing children, can call two different shapes equal.

    Testing

    Trees are written in level order, with nil for a missing child.

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