Problem
Given the roots of two binary trees root
and subRoot
, return true
if there is a subtree of root
with the same structure and node values of subRoot
and false
otherwise.
A subtree of a binary tree tree
is a tree that consists of a node in tree
and all of this node’s descendants. The tree tree
could also be considered as a subtree of itself.
OR
You have two very large binary trees: T1, with millions of nodes, and T2, with hundreds of nodes. Create an algorithm to decide if T2 is a subtree of T1.
Examples
Example 1:
graph LR subgraph root A(3) A --- B(4) A --- C(5) subgraph " " B --- D(1) B --- E(2) end end subgraph subRoot D2(4) D2 --- A2(1) D2 --- B2(2) end root ~~~ subRoot
|
|
Example 2:
graph LR subgraph root C(3) C --- D(4) C --- E(5) D --- A(1) D --- B(2) B --- F(0) B ~~~ N1:::hidden end subgraph subRoot D2(4) D2 --- A2(1) D2 --- B2(2) end root ~~~ subRoot classDef hidden display:none
|
|
Solution
Lets denote tree with root
as p
and tree with subRoot
as q
.
Video explanation
Here is the video explaining this method in detail. Please check it out:
Method 1 - Using Recursive DFS and Same Tree
We have already seen - Same Tree. Even though method 1 is faster asymptotically (if we use KMP), still that is not that clean.
See the example 2, leaf node in subRoot are not same as root, even though tree matches. So, subtrees have to be same after a somepoint.
Code
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(p + q)
- 🧺 Space complexity:
O(|p|+|q|)
, where |p| is the number of nodes in treep
and |q| is the number of nodes in treeq
.- The recursion stack for
isSubtree
can go as deep as O(|p|) in the worst case. - For each call to
isSameTree
, the recursion stack can go as deep as O(|q|). - So, the total space used by the recursion stack is O(|p| + |q|) in the worst case.
- The recursion stack for
Method 2 - Using Tree Serialization with preorder or postorder traversal
Intuition
The recursive approach can be inefficient due to repeated comparisons. A more optimal method is to represent each tree by a unique string. If the subRoot
’s string is a substring of the root
’s string, we have a match.
Approach
-
Serialization Strategy: We must convert the tree to a string in a way that is unambiguous.
- Traversal Order: A pre-order or post-order traversal is required. This is because they keep the root’s value at a fixed position relative to its children, ensuring a unique mapping from tree to string. An in-order traversal will not work as it can produce the same string for different tree structures.
- Separators & Nulls: We must use a separator (e.g.,
#
) to distinguish values (like1,2
from12
) and a special marker (e.g.,n
) fornull
children to perfectly preserve the tree’s structure.
-
The Algorithm:
- Generate the unique string for
root
using the serialization strategy (e.g., pre-order). - Generate the unique string for
subRoot
using the same strategy. - Check if the
subRoot
string is a substring of theroot
string. Most languages provide a built-in function for this.
- Generate the unique string for
Complexity
- ⏰ Time complexity:
O(m + n)
. Generating the strings takesO(m)
andO(n)
. The substring search is also typicallyO(m + n)
. - 🧺 Space complexity:
O(m + n)
to store the two generated strings.
Code
|
|
|
|
Method 3 - Using Inorder and Postorder Traversal
Algorithm
- Perform inorder traversal of tree
p
and store it as stringinorderP
. - Perform inorder traversal of tree
q
and store it as stringinorderQ
. - Perform postorder traversal of tree
p
and store it as stringpostorderP
. - Perform postorder traversal of tree
q
and store it as stringpostorderQ
. - Tree q is a subtree of tree p if:
inorderP
containsinorderQ
(matches the inorder sequence)postorderP
containspostorderQ
(matches the postorder structure within the identified sequence)
Example
Lets look at example 1:
graph LR subgraph root A(3) A --- B(4) A --- C(5) subgraph " " B --- D(1) B --- E(2) end end subgraph SubRoot D2(4) D2 --- A2(1) D2 --- B2(2) end
Lets look at inorder
:
inorderP
=1 4 2 3 5
inorderQ
=1 4 2
inorderP.contains(inorderQ)
istrue
Now, lets look at postorder
:
postorderP
=1 2 4 5 3
postorderQ
=1 2 4
postorderP.contains(postorderQ)
istrue
Code
|
|
Complexity
- ⏰ Time complexity:
O(|p|+|q|)
. Generating inorder and postorder traversal takeO(p)
andO(q)
, butcontains
may takeO(p)
assuming KMP algorithm. (#TODO link KMP algorithm). If we use brute forcecontains
, then, it will takeO(pq)
. - 🧺 Space complexity:
O(|p|+|q|)