The problem
You get the root of a binary tree with unique values, and two different values x and y that are both in the tree. Two nodes are cousins if they are at the same depth but have different parents. Return True if the nodes holding x and y are cousins.
Input: 1
/ \
2 3
\ \
4 5
x = 4, y = 5
Output: True4 and 5 are both at depth 2, and their parents are 2 and 3, which differ.
How to think about it
Start with the definition. “Cousins” is two conditions: same depth and different parent. Siblings (same parent) are at the same depth but don’t count. So for each of x and y, all you need is two facts: its parent and its depth. Once you have them, the answer is one comparison.
Brute force would overthink it. You could compute the full path from the root to each node and compare path lengths and the second-last elements. It works, but you store whole paths for nothing.
The key insight: one traversal that carries (parent, depth) down with it. When you go from a node to its child, the child’s parent is the current node and its depth is one more. When you reach the target value, return the pair. This is a small DFS helper, called once for x and once for y.
Why it sits in the BFS lesson. It’s a level question. A BFS version processes one level at a time and checks whether x and y both appear on the same level with different parents. Both are O(n). The DFS helper here is shorter to write, and it is what the tested source uses.
The tell is “same level/depth” combined with “relationship between two specific nodes”: collect depth and parent for each, then compare.
Approach
- Write
info(node, parent, depth, target):- return
NoneifnodeisNone, - return
(parent, depth)ifnode.val == target, - otherwise search the left subtree, then the right, with
depth + 1andnode.valas the parent.
- return
- Get
(px, dx)forxand(py, dy)fory. - Return
dx == dy and px != py.
Solution
def is_cousins(root, x, y):
def info(node, parent, depth, target): # returns (parent value, depth) of target
if not node: return None
if node.val == target: return (parent, depth)
return info(node.left, node.val, depth + 1, target) or \
info(node.right, node.val, depth + 1, target)
px, dx = info(root, None, 0, x)
py, dy = info(root, None, 0, y)
return dx == dy and px != py # same depth, different parent
# is_cousins([1,2,3,null,4,null,5], 4, 5) -> TrueThe or works because a found result is a non-empty tuple, which is truthy even when it is (None, 0).
Dry run
On the example, the search for x = 4 and then y = 5:
| Search | Nodes visited (value, depth) | Result (parent, depth) |
|---|---|---|
| x = 4 | (1, 0), (2, 1), (4, 2) | (2, 2) |
| y = 5 | (1, 0), (2, 1), (4, 2), (3, 1), (5, 2) | (3, 2) |
| compare | depths 2 == 2, parents 2 != 3 | True |
Complexity
- Time: O(n). Each search visits each node at most once, and there are two searches.
- Space: O(h), the recursion depth, where
his the tree height: O(log n) if balanced, O(n) if it’s a chain.
Edge cases and pitfalls
- Siblings:
is_cousins([1,2,3], 2, 3)isFalse; same depth, same parent. - One of them is the root: the root has depth 0 and no other node does, so the answer is
False. - Different depths:
False, even if the parents differ. - Comparing parent values is safe only because values are unique. If they weren’t, compare parent nodes instead.
- Returning early in BFS when only one is found. If a level holds
xbut noty, the answer is alreadyFalse; don’t keep searching deeper.
Related problems
- Binary Tree Level Order Traversal (LeetCode 102): the level-by-level loop the BFS version of this uses.
- Lowest Common Ancestor of a Binary Tree (LeetCode 236): another question about how two nodes relate in a tree.
- Maximum Depth of Binary Tree (LeetCode 104): passing depth down a recursive traversal.
- 1.Average of Levels in Binary Tree (LeetCode 637)
- 2.Minimum Depth of Binary Tree (LeetCode 111)
- 3.Cousins in Binary Tree (LeetCode 993)
- 4.Binary Tree Level Order Traversal (LeetCode 102)
- 5.Binary Tree Level Order Traversal II (LeetCode 107)
- 6.Binary Tree Right Side View (LeetCode 199)
- 7.Binary Tree Zigzag Level Order Traversal (LeetCode 103)
- 8.Populating Next Right Pointers in Each Node (LeetCode 116)
- 9.Rotting Oranges (LeetCode 994)
- 10.Word Ladder (LeetCode 127)