Start learning free

LESSON 418 OF 539 · BINARY TREES

Subtree of Another Tree in C#: Compare at Every Node, O(n * m)

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

    C# · BINARY TREES

    Subtree Of Another Tree in C#, explained

    Problem overview

    IsSubtree receives two binary trees, root and subRoot, and returns a bool: is there a node in root whose whole subtree, that node and every descendant, is exactly subRoot? "Exactly" means the same shape and the same value in every position. The lesson provides the TreeNode class (val, left, right), as the starter code's comment shows.

    With root = [3,4,5,1,2] and subRoot = [4,1,2], the node 4 with its children 1 and 2 matches, so the answer is true. If the 2 also had a child 0, the answer would be false: a subtree takes every descendant of its node, so no node of root gives back subRoot. A tree is also a subtree of itself, and with root = [1,1] and subRoot = [1] the answer is true, because the left child 1 has nothing below it.

    Approach: try every node as the starting point

    Split the question in two:

    1. Given two trees, are they identical? A helper, IsSameTree, answers that.
    2. Which node of root should be compared with subRoot? Any of them might be the one, so try all.

    The second part is a recursion over root: subRoot is a subtree of root if it equals the tree at root itself, or if it is a subtree of the left child, or of the right child. This visits every node once as a candidate starting point.

    Each comparison stops as soon as the trees differ, and it can never go past the size of subRoot, because a comparison only continues while both trees still have nodes. With n nodes in root and m in subRoot, there are at most n comparisons of at most m nodes each: O(n * m) time.

    Step by step

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

    1. Base case: root == null returns 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 past a leaf.
    2. Compare at root itself. If IsSameTree(root, subRoot) is true, return true and stop searching.
    3. Otherwise search lower. Return IsSubtree(root.left, subRoot) || IsSubtree(root.right, subRoot). The || operator skips the right side when the left side already found a match.
    4. Write IsSameTree(TreeNode p, TreeNode q), returning bool.
    5. Both null means equal. Two empty trees are the same tree. This also ends the recursion when both sides run out at the same moment.
    6. Exactly one null means different. Both-empty was handled above, so if either is null, one tree has a node where the other has none: the shapes differ.
    7. Different values mean different trees. Both nodes exist here, so reading p.val and q.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: two recursive calls joined with &&.

    The solution in C#

    class Solution {
        public bool 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 bool 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);
        }
    }
    

    C# details worth knowing:

    • null and reference types. TreeNode is a class, so a variable of that type holds a reference, and a missing child is null. Writing p.val when p is null throws NullReferenceException, which is why the null checks come before the value check.
    • == on nodes compares references. TreeNode doesn't define its own equality, so p == q would ask whether both variables point at the very same object, not whether the trees look alike. That is why the value comparison is written out by hand.
    • Short-circuit operators. && and || evaluate their right side only when needed. In IsSameTree this means a difference found on the left prevents any work on the right.
    • Method names are PascalCase. C# convention (and the lesson's tests) use IsSubtree and IsSameTree, while the node fields keep the lowercase names val, left and right.

    Complexity

    Time: O(n * m). IsSubtree reaches each of the n nodes of root once. At each one, IsSameTree visits at most m node pairs, because it only goes deeper while both trees still have nodes. In practice most comparisons end at the first mismatching value.

    Space: O(h) for the call stack, where h is the height of root. IsSubtree goes at most that deep, and IsSameTree never goes deeper than the height of subRoot. A tree that is a straight line makes h equal to n; the lesson limits depth to 400 levels, which is safe for recursion.

    Edge cases and common mistakes

    • A tree is a subtree of itself. root = [1] and subRoot = [1] returns true, because the check at root comes first.
    • Matching values is not enough. With root = [1,2] and subRoot = [2,3] the result is false. And with root = [1,null,2] and subRoot = [1,2] it is also false: the 2 is a right child in one tree and a left child in the other.
    • Extra descendants. Checking only that subRoot's nodes appear somewhere below a node is wrong. Example 2 has the right values below the 4, but the extra 0 makes the trees different. IsSameTree handles this because a node with a child facing a node without one hits the "exactly one null" check.
    • Forgetting the base case in IsSubtree. Without the root == null check, the recursion would pass a null to IsSameTree, and then IsSubtree(root.left, ...) would throw when root is null.
    • Mixing up && and ||. Comparing children needs both sides to match (&&); searching for the start needs only one side to contain the match (||).

    Testing

    Building the trees in a small program and calling IsSubtree gives these results:

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