100. Same Tree
Problem Description
You are given the roots of two binary trees p
and q
. Your task is to determine if these two binary trees are identical.
Two binary trees are considered the same when:
- They have the exact same structure (same shape and arrangement of nodes)
- Every corresponding node in both trees has the same value
For example:
- If both trees are empty (null), they are the same
- If one tree is empty and the other isn't, they are different
- If both trees have nodes, each node at the same position must have the same value
The solution uses a recursive depth-first search (DFS) approach. It checks three key conditions:
- Base case: If both nodes are null (
p == q
), returnTrue
since empty trees are identical - Mismatch detection: If only one node is null, or if both exist but have different values, return
False
- Recursive check: If the current nodes match, recursively verify that both the left subtrees are identical AND the right subtrees are identical
The algorithm traverses both trees simultaneously, comparing nodes at each position. It returns True
only when all corresponding nodes throughout both trees match perfectly in both structure and value.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: Binary trees are a special type of graph where each node has at most two children and there are no cycles. The problem involves traversing and comparing two tree structures.
Is it a tree?
- Yes: The problem explicitly states we are working with binary trees. We need to compare the structure and values of two binary tree data structures.
DFS
- Conclusion: Since we're dealing with trees, the flowchart leads us directly to DFS (Depth-First Search).
The flowchart suggests using DFS for this tree problem, which makes perfect sense because:
- We need to traverse both trees completely to ensure they are identical
- We need to compare nodes at corresponding positions in both trees
- DFS allows us to recursively check each subtree, comparing left with left and right with right
- The recursive nature of DFS naturally matches the recursive structure of trees
This aligns perfectly with our solution approach where we recursively compare each node and its subtrees using DFS traversal.
Intuition
When comparing two trees to see if they're identical, think about how you would manually verify this. You'd start at the roots and check: are both roots present? Do they have the same value? Then you'd look at their children and repeat the same questions.
This naturally suggests a recursive approach. At any given pair of nodes, we need to verify three things:
- The nodes themselves match (either both null or both have the same value)
- Their left subtrees are identical
- Their right subtrees are identical
The key insight is that two trees are the same if and only if their roots match AND their corresponding subtrees match. This breaks down the problem into smaller subproblems of the same type - comparing smaller trees.
Consider the edge cases first:
- If both nodes are
null
, they're trivially the same (empty trees are identical) - If only one is
null
, they can't be the same (one exists, one doesn't) - If both exist but have different values, they're different
Once we've handled these base cases, we can recursively apply the same logic to the children. The beauty of this approach is that each recursive call handles a smaller portion of the tree, and we only need to return True
if ALL corresponding parts match.
The recursion naturally handles the tree traversal for us - we don't need to explicitly manage a stack or queue. Each function call implicitly keeps track of which nodes we're currently comparing, and the call stack ensures we visit every node pair exactly once.
Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.
Solution Approach
The DFS recursive method provides an elegant solution to this problem. Let's walk through the implementation step by step:
Base Cases:
- If both nodes
p
andq
point to the same memory location (including both beingnull
), they're identical:if p == q: return True
- If only one node is
null
or the values don't match, the trees are different:if p is None or q is None or p.val != q.val: return False
Recursive Case: Once we've verified the current nodes match, we recursively check both subtrees:
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
The algorithm works by:
- Starting at the root nodes of both trees
- Checking if the current nodes are structurally and value-wise identical
- If they match, recursively verifying the left subtrees are the same
- Recursively verifying the right subtrees are the same
- Returning
True
only if all checks pass
The and
operator ensures both subtrees must be identical - if either recursive call returns False
, the entire expression evaluates to False
.
Time Complexity: O(min(m, n))
where m
and n
are the number of nodes in trees p
and q
. In the worst case (when trees are identical), we visit every node once.
Space Complexity: O(min(m, n))
for the recursion call stack. In the worst case of a skewed tree, the depth could be equal to the number of nodes.
The solution leverages the recursive nature of trees - a tree is the same as another if their roots match and their subtrees are recursively the same. This divide-and-conquer approach naturally handles all the structural comparisons needed.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through comparing two small binary trees to see how the recursive solution works:
Tree p:
1 / \ 2 3
Tree q:
1 / \ 2 3
Step-by-step execution:
-
Compare roots (p=1, q=1):
- Both nodes exist ✓
- Both have value 1 ✓
- Proceed to check subtrees
-
Check left subtrees (p.left=2, q.left=2):
- Both nodes exist ✓
- Both have value 2 ✓
- Check 2's children:
- Left children: both null ✓ (returns True)
- Right children: both null ✓ (returns True)
- Node 2 comparison returns True
-
Check right subtrees (p.right=3, q.right=3):
- Both nodes exist ✓
- Both have value 3 ✓
- Check 3's children:
- Left children: both null ✓ (returns True)
- Right children: both null ✓ (returns True)
- Node 3 comparison returns True
-
Final result: Since root values match (1==1) AND left subtrees match (True) AND right subtrees match (True), the function returns True.
Example of trees that aren't the same:
Tree p:
1 / 2
Tree q:
1 \ 2
- Compare roots (p=1, q=1): values match ✓
- Check left subtrees: p.left=2, q.left=null
- One is null, one isn't ✗
- Returns False immediately
The function returns False without checking the right subtrees since the left subtrees already differ.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9
10
11class Solution:
12 def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
13 """
14 Determines if two binary trees are identical.
15 Two trees are identical if they have the same structure and node values.
16
17 Args:
18 p: Root node of the first binary tree
19 q: Root node of the second binary tree
20
21 Returns:
22 True if both trees are identical, False otherwise
23 """
24 # Base case: both nodes are None (reached leaf nodes simultaneously)
25 if p is None and q is None:
26 return True
27
28 # If only one node is None or values don't match, trees are different
29 if p is None or q is None or p.val != q.val:
30 return False
31
32 # Recursively check if left subtrees and right subtrees are identical
33 # Both subtrees must be identical for the trees to be the same
34 left_same = self.isSameTree(p.left, q.left)
35 right_same = self.isSameTree(p.right, q.right)
36
37 return left_same and right_same
38
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Determines if two binary trees are structurally identical
19 * and have the same node values.
20 *
21 * @param p The root node of the first binary tree
22 * @param q The root node of the second binary tree
23 * @return true if both trees are identical, false otherwise
24 */
25 public boolean isSameTree(TreeNode p, TreeNode q) {
26 // Base case: both nodes are the same reference (including both null)
27 if (p == q) {
28 return true;
29 }
30
31 // Check if one node is null while the other isn't, or if values differ
32 if (p == null || q == null || p.val != q.val) {
33 return false;
34 }
35
36 // Recursively check if left subtrees are identical AND right subtrees are identical
37 return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
38 }
39}
40
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Determines if two binary trees are identical.
16 * Two binary trees are considered the same if they are structurally identical
17 * and the nodes have the same values.
18 *
19 * @param p - Pointer to the root of the first binary tree
20 * @param q - Pointer to the root of the second binary tree
21 * @return true if both trees are identical, false otherwise
22 */
23 bool isSameTree(TreeNode* p, TreeNode* q) {
24 // Base case: both nodes are null (same empty subtree)
25 if (p == nullptr && q == nullptr) {
26 return true;
27 }
28
29 // If one node is null and the other is not, trees are different
30 if (p == nullptr || q == nullptr) {
31 return false;
32 }
33
34 // If values at current nodes don't match, trees are different
35 if (p->val != q->val) {
36 return false;
37 }
38
39 // Recursively check if left subtrees and right subtrees are identical
40 return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
41 }
42};
43
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Determines if two binary trees are structurally identical and have the same node values.
17 *
18 * @param p - The root node of the first binary tree
19 * @param q - The root node of the second binary tree
20 * @returns true if both trees are identical, false otherwise
21 *
22 * Time Complexity: O(n) where n is the number of nodes in the smaller tree
23 * Space Complexity: O(h) where h is the height of the tree (recursion stack)
24 */
25function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
26 // Base case: both nodes are null, trees are identical at this point
27 if (p === null && q === null) {
28 return true;
29 }
30
31 // If one node is null and the other isn't, or if values don't match, trees are different
32 if (p === null || q === null || p.val !== q.val) {
33 return false;
34 }
35
36 // Recursively check if left subtrees and right subtrees are identical
37 return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
38}
39
Time and Space Complexity
The time complexity is O(min(m, n))
, where m
and n
are the number of nodes in trees p
and q
respectively. In the worst case, when both trees are identical or differ only at the deepest leaf, we need to visit every node in the smaller tree. The algorithm terminates as soon as a mismatch is found or when we reach the end of the smaller tree, hence we visit at most min(m, n)
nodes.
The space complexity is O(min(m, n))
. This space is used by the recursive call stack. In the worst case, when comparing two completely unbalanced trees (like linked lists), the recursion depth can reach the height of the smaller tree. For a skewed tree, the height equals the number of nodes. In the best case of balanced trees, the space complexity would be O(log(min(m, n)))
, but we consider the worst case. The recursion stops when we reach the end of the smaller tree, so the maximum recursion depth is bounded by the number of nodes in the smaller tree.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Null Checking Order
A common mistake is checking node values before verifying both nodes exist, which leads to AttributeError
when trying to access .val
on a None
object.
Incorrect approach:
def isSameTree(self, p, q):
if p.val != q.val: # Error if p or q is None!
return False
if p is None and q is None:
return True
# ... rest of the code
Correct approach:
def isSameTree(self, p, q):
# Check for None values FIRST
if p is None and q is None:
return True
if p is None or q is None:
return False
# Now safe to access .val
if p.val != q.val:
return False
# ... rest of the code
2. Using OR Instead of AND for Recursive Calls
Some developers mistakenly use or
when combining the recursive results, thinking "if either subtree is the same, return True."
Incorrect approach:
return self.isSameTree(p.left, q.left) or self.isSameTree(p.right, q.right)
This would incorrectly return True
if only one subtree matches, even when the other differs.
Correct approach:
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
Both subtrees must be identical for the entire tree to be considered the same.
3. Forgetting to Handle the Case Where Both Nodes Exist but Have Different Values
Some implementations check for null cases but forget to compare values when both nodes exist.
Incomplete approach:
def isSameTree(self, p, q):
if p is None and q is None:
return True
if p is None or q is None:
return False
# Missing value comparison!
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
This would incorrectly consider trees with different node values as identical if they have the same structure.
4. Comparing Node References Instead of Values
Using ==
to compare entire nodes instead of their values can lead to unexpected behavior.
Potentially problematic:
if p == q: # This compares object references return True
While this works when both are None
, it might not work as expected for non-None nodes unless they're the exact same object in memory. For clarity and correctness, explicitly check:
if p is None and q is None: return True if p is None or q is None: return False if p.val != q.val: return False
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!