Subtree of Another Tree
An easy problem from Trees. Try it first, open a hint when you are stuck, and read the solution when you are done.
- Easy
- Trees
The problem
root and subRoot are the top nodes of two binary trees. Either can be null, which means an empty tree. Each node has a number val and up to two children, left and right; a missing child is null. A subtree is a node together with every node below it. Return true if the first tree has a subtree that is exactly the second tree: the same shape, with the same value in every position. Otherwise return false. When subRoot is null, return true: an empty tree counts as found.
What to write
type TreeNode = {val:number;left:TreeNode|null;right:TreeNode|null};
function solve(root: TreeNode | null, subRoot: TreeNode | null): booleanExample 1
Input root = {"val":3,"left":{"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":null,"right":null}},"right":{"val":5,"left":null,"right":null}} subRoot = {"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":null,"right":null}} Output true
In the first tree, the node 4 has a left child 1 and a right child 2, and neither of them has children. That subtree has the same shape and the same values as the second tree, so the answer is true.
Example 2
Input root = {"val":3,"left":{"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":{"val":0,"left":null,"right":null},"right":null}},"right":{"val":5,"left":null,"right":null}} subRoot = {"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":null,"right":null}} Output false
The first tree again has a node 4 with children 1 and 2, but this time the 2 has a child of its own, 0. The subtree at 4 therefore has one node more than the second tree, and no other subtree of the first tree matches either.
Constraints
Both inputs are binary trees represented by {val, left, right}; an empty subRoot matches.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Use the constraints
A copy of
subRootcan start at any node ofroot, not only at the top. It must also be complete: that node may not have extra nodes below it thatsubRootlacks. Either tree can be empty, so decide those two cases first.Hint 2: Ask a smaller question
Point at one node of
root. How would you check, by hand, that this node and everything below it are exactlysubRoot? And if they are not, where could a copy still be?Hint 3: Name the pattern
A tree contains a copy of
subRootif the copy starts at its top node, or the left child's subtree contains one, or the right child's subtree does. So a function can answer for a node by calling itself on the two children. Building a node's answer from its children's answers is postorder tree recursion. Read the lesson on postorder tree recursion.Hint 4: State the invariant
A call on a node answers for the whole subtree that starts there: does that subtree contain an exact copy of
subRoot? The test at one node walks both trees together and says yes only when every pair of nodes agrees. A missing child agrees only with another missing child.Hint 5: Sketch the pseudocode
same(a, b): if either node is missing, return whether both are missing. Otherwise return: the values are equal, and same(left children), and same(right children). solve: if
subRootis empty, return true. Ifrootis empty, return false. Return same(root,subRoot), or solve(left child,subRoot), or solve(right child,subRoot).
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Approach
At each root candidate, compare the entire subtree. Otherwise continue searching both child subtrees.
The slow way
Write each tree's values in a row: a node, then everything under its left child, then everything under its right child. Look for subRoot's row inside root's row. It is tempting but wrong, because a row of values forgets the shape. In example 2 the rows are 3, 4, 1, 2, 0, 5 and 4, 1, 2. The second sits inside the first, yet the answer is false.
The insight
A copy of subRoot has to start at some node of root. So ask one yes-or-no question at every node: is the subtree here exactly subRoot? That question is the Same Tree problem: walk both trees together, position by position, and a missing child must face a missing child.
Step by step
- To compare two trees: two missing nodes match, one alone does not. Two missing nodes in the same position mean both trees end here, which agrees. If only one is missing, one tree has a node that the other lacks.
- Otherwise the values must be equal and both pairs of children match. Equal values at the top are not enough. The same test runs on the two left children and on the two right children, and so on down, until a pair disagrees or both trees end.
- An empty subRoot counts as found in any tree. The problem defines it that way. Checking it first means that from here on
subRoothas at least one node. - An empty root has no node where a copy could start.
subRoothas a node, and an empty tree has none to match it. This is also where the search ends below a node with a missing child. - The copy starts at this node, or somewhere under one of its children. Those are the only places a copy can be. If the comparison at this node fails, the same search runs on the left child and, if that finds nothing, on the right child.
The same numbered steps are the comments in each solution.
JavaScript
function solve(root, subRoot) {
function same(a, b) {
// 1. To compare two trees: two missing nodes match, one alone does not
if (!a && !b) return true;
if (!a || !b) return false;
// 2. Otherwise the values must be equal and both pairs of children match
if (a.val !== b.val) return false;
return same(a.left, b.left) && same(a.right, b.right);
}
// 3. An empty subRoot counts as found in any tree
if (!subRoot) return true;
// 4. An empty root has no node where a copy could start
if (!root) return false;
// 5. The copy starts at this node, or somewhere under one of its children
return (
same(root, subRoot) ||
solve(root.left, subRoot) ||
solve(root.right, subRoot)
);
}TypeScript
type TreeNode = { val: number; left: TreeNode | null; right: TreeNode | null };
function solve(root: TreeNode | null, subRoot: TreeNode | null): boolean {
function same(a: TreeNode | null, b: TreeNode | null): boolean {
// 1. To compare two trees: two missing nodes match, one alone does not
if (!a && !b) return true;
if (!a || !b) return false;
// 2. Otherwise the values must be equal and both pairs of children match
if (a.val !== b.val) return false;
return same(a.left, b.left) && same(a.right, b.right);
}
// 3. An empty subRoot counts as found in any tree
if (!subRoot) return true;
// 4. An empty root has no node where a copy could start
if (!root) return false;
// 5. The copy starts at this node, or somewhere under one of its children
return (
same(root, subRoot) ||
solve(root.left, subRoot) ||
solve(root.right, subRoot)
);
}
Python
def solve(root, subRoot):
def same(a, b):
# 1. To compare two trees: two missing nodes match, one alone does not
if not a and not b:
return True
if not a or not b:
return False
# 2. Otherwise the values must be equal and both pairs of children match
if a.val != b.val:
return False
return same(a.left, b.left) and same(a.right, b.right)
# 3. An empty subRoot counts as found in any tree
if not subRoot:
return True
# 4. An empty root has no node where a copy could start
if not root:
return False
# 5. The copy starts at this node, or somewhere under one of its children
return (
same(root, subRoot)
or solve(root.left, subRoot)
or solve(root.right, subRoot)
)
Example 2, traced
Input root = {"val":3,"left":{"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":{"val":0,"left":null,"right":null},"right":null}},"right":{"val":5,"left":null,"right":null}} subRoot = {"val":4,"left":{"val":1,"left":null,"right":null},"right":{"val":2,"left":null,"right":null}} Output false
| Node of root | Compared with subRoot | Result |
|---|---|---|
| 3 | 3 is not 4 | Values differ: no copy here |
| 4 | 4 = 4, 1 = 1, 2 = 2, 0 against no node | An extra node: no copy here |
| 1 | 1 is not 4 | Values differ: no copy here |
| 2 | 2 is not 4 | Values differ: no copy here |
| 0 | 0 is not 4 | Values differ: no copy here |
| 5 | 5 is not 4 | Values differ: no copy here |
| No node is left | - | Return false |
Why it works
The search asks at every node of root whether the subtree there is exactly subRoot, because it moves on to both children whenever the answer is no. The comparison says yes only if every value agrees and every missing child faces a missing child. So a yes is a real copy, and no copy is skipped.
Time and space
Time O(n m), extra space O(h). A comparison can start at each of the n nodes of root and walk all m nodes of subRoot: O(n m) time. The unfinished calls lie along one path down root, so the space is O(h), where the height h counts the nodes on the longest path down root.
n and m are input sizes, h is a tree's height, V and E are a graph's vertices and edges. The bounds describe this reference solution.
Watch out
- The comparison must not stop when
subRootruns out of nodes: the other tree has to run out at the same place. Otherwise example 2 returns true, although the 2 inroothas a child thatsubRootlacks. - A failed comparison at one node is not the final answer. The copy can start lower down, so the search has to go on into both children. With only the top node compared, example 1 returns false.
- Check for a missing node before you read its value. Whenever two nodes match, the comparison goes on to their children, missing ones included, and reading the value of a missing node stops the program with an error.
Pattern
Postorder tree recursion. Builds on Same Tree.