Problem overview
The function isSameTree receives the roots of two binary trees, p and q (the lesson provides TreeNode, with Val, Left and Right), and returns true when they are the same tree. That means the same shape, and the same value at every position. Two empty trees are the same.
Trees written in level order, with nil for a missing child, give these results:
p = [1, 2, 3]andq = [1, 2, 3]givetrue.p = [1, 2]andq = [1, nil, 2]givefalse: the values are the same, but the 2 is the left child of the root inpand the right child inq.p = [1, 2, 1]andq = [1, 1, 2]givefalse: the shapes match, but the left children hold 2 and 1.
Approach: compare the roots, then the subtrees
A tree is defined by its root value, its left subtree and its right subtree. So 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. The two subtree questions have the same form as the original, which is what recursion needs.
Before comparing values, the function has to settle whether each node exists. There are four cases for a pair of positions: both empty, one empty, both present with different values, and both present with equal values. The first three give an answer at once. Only the last one needs to look deeper.
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, or either one when they match.
Step by step
- Both are nil: if
p == nil && q == nil, returntrue. Two empty trees are the same. This also stops the recursion when both sides step past a leaf together. - Only one is nil: if
p == nil || q == nil, returnfalse. The both-empty case has already returned, so exactly one tree is empty here, and the shapes differ. - The values differ: if
p.Val != q.Val, returnfalse. Both nodes exist at this point, so readingValis safe. - Compare the children: return
isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right). The roots already match, so the trees are the same exactly when both pairs of subtrees are.
The solution in Go
func isSameTree(p *TreeNode, q *TreeNode) bool {
if p == nil && q == nil {
return true
}
if p == nil || q == nil {
return false
}
if p.Val != q.Val {
return false
}
return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right)
}
Go details worth knowing:
- Nil pointers mark missing nodes. A child that doesn't exist is a
nil*TreeNode, and comparing a pointer withnilis how the function tells the cases apart. Readingp.Valon a nil pointer would panic, which is why the two nil tests come before the value test. - The order of the tests matters. The "one is nil" test relies on the "both are nil" test above it having returned already. Swapping them would make two empty trees answer
false. - Short-circuit
&&. If the left subtrees differ, the right ones are never compared, so a difference on the left ends the work early. - Pointer comparison is not enough.
p == qwould compare addresses: two separately built trees with identical content are different pointers. The function compares the contents, so it answerstruefor them.
Complexity
Time: O(n). Each pair of nodes is compared at most once, and each comparison takes constant time. The function stops early as soon as it finds a difference.
Space: O(h) for the recursion, where h is the height of the trees. The statement limits the depth to 400 levels.
Edge cases and common mistakes
- Two empty trees.
isSameTree(nil, nil)returnstrue. - One empty tree.
nilagainst[1]returnsfalse. - Same values, different shape.
[1, 2]and[1, nil, 2]hold the same values, but the answer isfalse. Comparing the lists of values alone would miss this, which is why missing children count as part of the comparison. - One tree is a prefix of the other.
[1, 2]against[1, 2, 3]returnsfalse: at the right child, one side isniland the other holds 3. - Single equal nodes.
[7]against[7]returnstrue, and both children pairs arenil, which hit the first test. - Checking the value before the nil tests. It panics with a nil pointer dereference whenever the trees' shapes differ.
Testing
[1, 2, 3]and[1, 2, 3]givetrue.[1, 2]and[1, nil, 2]givefalse.[1, 2, 1]and[1, 1, 2]givefalse.nilandnilgivetrue.niland[1]givefalse.[7]and[7]givetrue.[1, 2]and[1, 2, 3]givefalse.
