Start learning free

LESSON 420 OF 541 · BINARY TREES

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

    SWIFT · BINARY TREES

    Subtree Of Another Tree in Swift, explained

    Problem overview

    isSubtree receives two optional TreeNode values, root and subRoot, and returns a Bool. The answer is true when some node of root, taken together with all of its descendants, forms exactly the same tree as subRoot: same shape and the same value in every place. A tree counts as a subtree of itself. The lesson provides the TreeNode class, with val, left and right and an init(_ x: Int).

    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, so it matches and the answer is true. In the second example, the 2 has an extra child 0 in root. The node 4 now carries more than subRoot does, and a subtree must take every descendant, so the answer is false.

    Approach: compare at every node

    The idea splits the problem in two. First, a way to tell whether two trees are identical: walk both together, and they match when both are empty, or when both roots hold the same value and the left subtrees match and the right subtrees match. Second, a way to try that comparison at every possible starting place: the match is either at root itself, or inside its left subtree, or inside its right subtree.

    With n nodes in root and m in subRoot, the comparison at one node stops after at most m nodes, since a comparison can't go deeper than subRoot does, and it is started at each of the n nodes. That gives O(n · m) time, which is the budget the lesson sets.

    Step by step

    1. Base case of isSubtree. Unwrap root with guard let and return false when it is nil. An empty tree has no node where subRoot could begin, and subRoot has at least one node. This also ends the recursion when it steps below a leaf.
    2. Try the current node. If the helper says root and subRoot are the same tree, return true at once.
    3. Try lower down. Otherwise return whether subRoot is a subtree of root.left or of root.right, joined with ||. Together with step 2, every node of root gets tried as the starting point.
    4. Write the helper's signature. isSameTree takes two TreeNode? parameters, p and q, without argument labels, and returns Bool.
    5. Both empty. If p and q are both nil, the trees are equal. This is where the recursion ends when both sides run out together.
    6. Only one empty. Unwrap both with one guard let. If either is nil, the case of both being empty has already returned, so exactly one is empty and the shapes differ: return false.
    7. Different values. Both nodes exist now, so compare p.val with q.val and return false if they differ.
    8. Compare the children. The roots match, so the trees are equal exactly when the left pair and the right pair are equal: two recursive calls joined with &&.

    The solution in Swift

    class Solution {
        func isSameTree(_ p: TreeNode?, _ q: TreeNode?) -> Bool {
            if p == nil && q == nil {
                return true
            }
            guard let p = p, let q = q else {
                return false
            }
            if p.val != q.val {
                return false
            }
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
        }
    
        func isSubtree(_ root: TreeNode?, _ subRoot: TreeNode?) -> Bool {
            guard let root = root else {
                return false
            }
            if isSameTree(root, subRoot) {
                return true
            }
            return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot)
        }
    }
    

    The Swift details worth a closer look:

    • guard let root = root creates a new, non-optional constant named root that hides the optional parameter for the rest of the function. The else block must leave the function, here with return false. After the guard, root.left and root.val need no ? or !.
    • p == nil works without Equatable. TreeNode is a plain class, but Swift lets any optional be compared with nil. What this does not do is compare two nodes: the lesson compares values and shape, never identity, so there is no === here.
    • Short-circuit operators. && skips its right side when the left one is false, and || skips the right side when the left one is true. A comparison stops at the first difference, and the search stops at the first match.
    • Passing optionals along. isSameTree(root, subRoot) passes a TreeNode where a TreeNode? is expected, which Swift wraps automatically.
    • Methods call each other by name. Inside a class, isSameTree and isSubtree call themselves and each other without self., and their order in the class doesn't matter.

    Complexity

    Time: O(n · m). isSubtree visits each of the n nodes of root once. At each, isSameTree runs for at most m steps, because it ends when subRoot's nodes are used up or a difference shows.

    Space: O(h) for the call stack, where h is the depth of root (at most 400 in this lesson). The code allocates nothing else.

    Edge cases and common mistakes

    • Matching only the top of the subtree. Comparing just the roots' values, or stopping when subRoot runs out, accepts a node that has extra descendants. The second example is that trap: the 4 with 1 and 2 under it looks right until the 0 below the 2 is reached. Requiring both sides to be nil at the same moment is what rejects it.
    • Checking root itself only. The match can be anywhere, so the recursion must continue into both children when the current node fails.
    • Equal values, different shapes. With root = [1,1] and subRoot = [1], the top 1 has a child, so it doesn't match, but its left child is a lone 1 and does. The answer is true.
    • Same values, different shape. With root = [1,2] and subRoot = [2,nil,3] the value 2 appears in both, but the 2 in root is a leaf and the one in subRoot has a right child. Comparing the nil children in the right places tells them apart, and the answer is false.

    Testing

    • 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, since a tree is a subtree of itself.
    • root = [1,2], subRoot = [2,nil,3] gives false.
    • root = [1,nil,2,nil,3], subRoot = [3] gives true: the leaf 3 at the bottom of a right-leaning chain.
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free