Problem overview
The method isSameTree(p, q) receives the roots of two binary trees and returns True when they are the same tree: exactly the same shape, with the same value in every position. Two empty trees are the same. The lesson provides the TreeNode class, with val, left and right, as shown in a comment in the starter code.
The trees [1, 2, 3] and [1, 2, 3] are the same. The trees [1, 2] and [1, None, 2] hold the same values, but the 2 is a left child in one and a right child in the other, so they differ. The trees [1, 2, 1] and [1, 1, 2] have the same shape but different values in the left child (2 against 1).
Approach: compare the roots, then the subtrees
Two trees are the same exactly when their roots hold the same value, their left subtrees are the same, and their right subtrees are the same. Each of the two subtree questions is the original question on smaller trees, so recursion fits. Before comparing values, the code has to handle missing nodes, since None has no value to read:
- Both nodes are missing: the trees are the same there.
- Exactly one is missing: the shapes differ.
- Both exist: compare their values, then recurse into the children.
Each pair of nodes is compared at most once, so the time is O(n), where n is the number of nodes in the smaller tree.
Step by step
- Both empty. If
not p and not q, returnTrue.not pis true whenpisNone. This case also ends the recursion when both trees run out at the same moment below a leaf. - Only one empty. If
not p or not q, returnFalse. The first test already handled both being empty, so exactly one is empty here. - Different values. If
p.val != q.val, returnFalse. Both nodes exist at this point, so reading.valis safe. - Compare the subtrees. Return
self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right). The roots match, so the trees are the same exactly when both pairs of subtrees are.
The solution in Python
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if not p and not q:
return True
if not p or not q:
return False
if p.val != q.val:
return False
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
Python details:
- Truthiness of nodes. A
TreeNodeobject is truthy andNoneis falsy, sonot pworks as "p is missing". It would be wrong for a node's value: a node holding 0 is still a node, and the code only appliesnotto the nodes, never top.val. !=compares values.p.val != q.valcompares the numbers. Comparing the nodes themselves with==would not do what this problem needs, because by default two different node objects are never equal, even with identical contents.- Short-circuit
and. If the left pair differs, the right pair is skipped. - Recursion depth. Each tree is at most 400 levels deep, well below Python's default limit of about 1,000 nested calls.
Complexity
The time is O(n), where n is the number of nodes in the smaller tree: the recursion stops at the first pair that differs, and when the trees are the same it visits every pair once. The space is the call stack, O(h) for height h.
Edge cases and common mistakes
- Two empty trees.
[]and[]are the same, and the answer isTrue. - One empty tree.
[]against[1], or[1]against[], isFalse. - Same values, different shape.
[1, 2]against[1, None, 2]isFalse. A check that compares only the values in some traversal order, without recording the missing children, would miss this. - Reading
.valtoo early. If the value comparison comes before the missing-node checks, it fails with anAttributeErrorwhen a node isNone. - Zero as a value.
[0]against[0, None]isTrue: a node holding 0 is a real node, and an explicitNonechild is the same as no child.
Testing
| p | q | result |
|---|---|---|
[1, 2, 3] | [1, 2, 3] | True |
[1, 2] | [1, None, 2] | False |
[1, 2, 1] | [1, 1, 2] | False |
[] | [] | True |
[] | [1] | False |
[1, 2, 3] | [1, 2, 4] | False |
