Binary Trees
Nodes with up to two children: the four ways to traverse them, and the recursive pattern most tree problems reduce to.
How It Works
A binary tree is a set of nodes where each one holds a value and up to two children, left and right. The top node is the root, and nodes with no children are leaves. A node's depth is the number of edges between it and the root, and the tree's height h is the number of edges on the longest path from the root down to a leaf. Most problems hand you the root and nothing else.
input: [1, 2, 3, 4, 5, null, 6]
1
/ \
2 3
/ \ \
4 5 6
preorder node, left, right 1 2 4 5 3 6
inorder left, node, right 4 2 5 1 3 6
postorder left, right, node 4 5 2 6 3 1
level order row by row 1 | 2 3 | 4 5 6Nearly every tree problem is solved with the same move: write a function that handles one node by calling itself on node.left and node.right, then combines the two answers. The base case is the empty tree, null. Trust that the recursive calls return the right answers for the subtrees, and only think about what to do with them at this node.
Preorder, Inorder and Postorder in One Walk
Step 1 of 13
root
call stack
written
- Current call and what it just wrote
- Waiting on the call stack
- Both subtrees finished
Start at the root, 1. A recursive walk reaches every node three times: on the way in, between its two subtrees and on the way out. Each order writes the node down at one of those moments.
All 13 steps as text
- Start at the root,
1. A recursive walk reaches every node three times: on the way in, between its two subtrees and on the way out. Each order writes the node down at one of those moments. - The call on
1starts, and preorder writes1before either subtree. Then it calls itself on the left child,2. - The call on
2starts, and preorder writes2before either subtree. Then it calls itself on the left child,4. 4is a leaf. Both of its children arenull, so all three orders write it back to back, and the call returns to2.- The left subtree of
2is done, so inorder writes2now, between its subtrees. Next comes the right child,5. 5is a leaf. Both of its children arenull, so all three orders write it back to back, and the call returns to2.- Both subtrees of
2are done, so postorder writes2last, and the call returns to1. - The left subtree of
1is done, so inorder writes1now, between its subtrees. Next comes the right child,3. - Preorder writes
3on the way in. Its left child isnull, so inorder writes it straight away too. Next comes the right child,6. 6is a leaf. Both of its children arenull, so all three orders write it back to back, and the call returns to3.- Both subtrees of
3are done, so postorder writes3last, and the call returns to1. - Both subtrees of
1are done, so postorder writes1last, and the walk is over. - Preorder
[1, 2, 4, 5, 3, 6], inorder[4, 2, 5, 1, 3, 6]and postorder[4, 5, 2, 6, 3, 1]all came from the same calls in the same order. Only the moment each node was written differs.
- Preorder (node, left, right) visits a node before its children. Use it when a parent passes something down: copying or serializing a tree, or tracking the path from the root.
- Inorder (left, node, right) visits a binary search tree in sorted order.
- Postorder (left, right, node) visits a node after its children, so it fits answers built from the children's answers: heights, sizes, diameters and common ancestors.
- Level order visits the tree row by row with a queue, which is breadth-first search. Use it for anything about levels or depth: per-level averages, the view from the right side, the minimum depth.
When to Reach for It
- The input is a
rootnode withleftandrightchildren. - A node's answer depends on its subtrees' answers: heights, sizes, sums, balance or paths.
- The question mentions levels, depth, or what you'd see looking at the tree from one side.
- You compare or combine two trees node by node, as in Same Tree, Symmetric Tree or Merge Two Binary Trees.
Complexity
| Operation | Time | Space |
|---|---|---|
| Preorder, inorder or postorder (recursive) | O(n) | O(h) call stack |
| Preorder, inorder or postorder (your own stack) | O(n) | O(h) stack |
| Level order (BFS with a queue) | O(n) | O(w), where w is the widest level |
| Find a value (no ordering to use) | O(n) | O(h) |
| Build from a level-order array | O(n) | O(n) |
The Template
Tree Traversals, Recursive and Iterative
The three recursive traversals share one shape: return on null, otherwise visit the node and recurse into both children. The only difference is where the visit goes: before both calls (preorder), between them (inorder) or after both (postorder). Each one passes a single result list down and appends to it, so the whole walk stays O(n).
Each one has an iterative twin that keeps its own stack in place of the call stack. The recursion hasn't gone anywhere. It's written out by hand: a node waiting on your stack is a call waiting for its turn. Interviewers often ask for these as a follow-up, and they're what you reach for when a tree is deep enough to overflow the call stack.
preorderIterative is the simplest. Pop a node, visit it, then push its children. A stack hands back the newest item first, so push the right child first: the left child lands on top, and its whole subtree is visited before the right child comes off.
Preorder With Your Own Stack
Step 1 of 8
root
stack
result
- Just popped and visited
- Waiting on the stack
- Visited
Push the root, 1. The stack holds the nodes still to visit, and whichever one is on top goes next.
All 8 steps as text
- Push the root,
1. The stack holds the nodes still to visit, and whichever one is on top goes next. - Pop
1and visit it. Push its right child3first and its left child2second, so the left one is on top and its whole subtree comes off before the right one. - Pop
2and visit it. Push its right child5first and its left child4second, so the left one is on top and its whole subtree comes off before the right one. - Pop
4and visit it. It's a leaf, so nothing is pushed.5is on top now: it was pushed as the right child of2and waited while the left side was visited. - Pop
5and visit it. It's a leaf, so nothing is pushed.3is on top now: it was pushed as the right child of1and waited while the left side was visited. - Pop
3and visit it. Its only child,6, goes on the stack and comes off next. - Pop
6and visit it. It's a leaf, so nothing is pushed. - The stack is empty, so the walk is done:
[1, 2, 4, 5, 3, 6], the same preorder the recursion writes. Swap the stack for a queue and push the left child first, and this loop visits level by level instead.
inorderIterative can't visit a node when it first reaches it, because the left subtree comes first. So it goes left as far as it can, pushing every node on the way down. The node it pops next has nothing unvisited on its left, so it visits that node and then walks the right subtree the same way. Kth Smallest Element in Binary Search Trees animates this loop.
postorderIterative is the trickiest, because a node has to wait for both subtrees. It runs the same go-left loop, but it peeks at the top instead of popping it. If the top has a right child that hasn't been walked, it walks that first and leaves the node where it is. Otherwise both sides are done, so it visits the node and pops it. last tells the two cases apart: a subtree's root is the last node postorder visits in it, so when last is the top's right child, that whole subtree is finished.
Postorder With Your Own Stack
Step 1 of 15
root
stack
result
- Pushed, checked or visited this step
- Waiting on the stack
- Visited
Postorder can't visit a node when it first reaches it, because both of its subtrees have to finish first. So each node waits on the stack, just like a recursive call, until they have.
All 15 steps as text
- Postorder can't visit a node when it first reaches it, because both of its subtrees have to finish first. So each node waits on the stack, just like a recursive call, until they have.
- Go left from
1as far as possible, pushing1,2and4. Their left sides come first, and each one stays on the stack until its right side is done too. 4is on top, and it's a leaf, so both of its subtrees are empty. Visit4, pop it and setlast = 4.2is on top. Its right child5isn'tlast, so its right subtree hasn't been walked yet. Leave2on the stack and walk5first.- Push
5. It has no left child, so it's on top straight away. 5is on top, and it's a leaf, so both of its subtrees are empty. Visit5, pop it and setlast = 5.2's right child5islast. A subtree's root is the last node postorder visits in it, so2's right subtree is done. Visit2, pop it and setlast = 2.1is on top. Its right child3isn'tlast, so its right subtree hasn't been walked yet. Leave1on the stack and walk3first.- Push
3. It has no left child, so it's on top straight away. 3is on top. Its right child6isn'tlast, so its right subtree hasn't been walked yet. Leave3on the stack and walk6first.- Push
6. It has no left child, so it's on top straight away. 6is on top, and it's a leaf, so both of its subtrees are empty. Visit6, pop it and setlast = 6.3's right child6islast. A subtree's root is the last node postorder visits in it, so3's right subtree is done. Visit3, pop it and setlast = 3.1's right child3islast. A subtree's root is the last node postorder visits in it, so1's right subtree is done. Visit1, pop it and setlast = 1.- The stack is empty:
[4, 5, 2, 6, 3, 1]. The stack always held exactly the calls the recursive version would have waiting, so this loop is that recursion written out by hand.
Level order goes across the tree instead of down it, which doesn't match the order recursion visits nodes. (You can fake it by passing the depth down and appending to that level's list, but the visiting order is still depth-first.) A queue gives you the real thing: take a node off the front, record it, and add its children to the back. Children always join behind everything left on the current level, so the tree comes out row by row. Taking exactly as many nodes as the queue holds at the start of a round gives you one level at a time.
Put levelOrder next to preorderIterative and it's the same loop: take a node out, record it, put its children in. Only the container differs. A stack hands back the newest node, so the walk dives down one path. A queue hands back the oldest, so it sweeps across.
Level Order With a Queue
Step 1 of 10
root
queue
levels
- Just dequeued and written
- Waiting in the queue
- Written to its level
Put the root, 1, in the queue. A queue hands nodes back oldest first. Each round takes exactly as many nodes as the queue holds when the round starts, which is one whole level.
All 10 steps as text
- Put the root,
1, in the queue. A queue hands nodes back oldest first. Each round takes exactly as many nodes as the queue holds when the round starts, which is one whole level. - Dequeue
1and write it on level 0. Its children2and3join the back of the queue. - Level 1 starts. The queue holds
2and3, exactly the nodes on level 1, so this round dequeues 2. Children added during the round queue up behind them. - Dequeue
2and write it on level 1. Its children4and5join the back of the queue, behind3. - Dequeue
3and write it on level 1. Its child6joins the back of the queue, behind4and5. - Level 2 starts. The queue holds
4,5and6, exactly the nodes on level 2, so this round dequeues 3. Children added during the round queue up behind them. - Dequeue
4and write it on level 2. It's a leaf, so nothing joins the queue. - Dequeue
5and write it on level 2. It's a leaf, so nothing joins the queue. - Dequeue
6and write it on level 2. It's a leaf, so nothing joins the queue. - The queue is empty:
[[1], [2, 3], [4, 5, 6]]. It's the same loop as preorder with a stack: take a node out, write it, put its children in. A stack dives down one path, while a queue sweeps across.
buildTree reads LeetCode's format: values in level order, with null where a child is missing. It hands the values out two at a time, left child then right, to the nodes in the order they were created. A missing node gets no entries for its own children, which keeps the array short for lopsided trees.
Time O(n) eachSpace O(n) for the output; O(h) for the call stack or your own stack, O(w) for the queue
from collections import deque
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def build_tree(values: list[int | None]) -> TreeNode | None:
"""Builds a tree from level-order values, where None marks a missing child.
This is how LeetCode writes trees down."""
first = values[0] if values else None
if first is None:
return None
root = TreeNode(first)
queue = deque([root])
i = 1
# Hand out the remaining values two at a time, left child then right, to
# the nodes in the order they were created.
while i < len(values):
node = queue.popleft()
left = values[i]
if left is not None:
node.left = TreeNode(left)
queue.append(node.left)
i += 1
right = values[i] if i < len(values) else None
if right is not None:
node.right = TreeNode(right)
queue.append(node.right)
i += 1
return root
def preorder(node: TreeNode | None, result: list[int]) -> list[int]:
"""Node, then left subtree, then right subtree."""
if node is None:
return result
result.append(node.val) # Visit the node before either subtree.
preorder(node.left, result)
preorder(node.right, result)
return result
def preorder_iterative(root: TreeNode | None) -> list[int]:
"""Preorder with your own stack in place of the call stack."""
result: list[int] = []
stack = [root] if root is not None else []
while stack:
node = stack.pop()
result.append(node.val)
# Push the right child first, so the left one comes off the stack first.
if node.right is not None:
stack.append(node.right)
if node.left is not None:
stack.append(node.left)
return result
def inorder(node: TreeNode | None, result: list[int]) -> list[int]:
"""Left subtree, then node, then right subtree."""
if node is None:
return result
inorder(node.left, result)
result.append(node.val) # Visit the node between its subtrees.
inorder(node.right, result)
return result
def inorder_iterative(root: TreeNode | None) -> list[int]:
"""Inorder with your own stack, where each node waits while its left
subtree is walked."""
result: list[int] = []
stack: list[TreeNode] = []
node = root
while node is not None or stack:
# Go left as far as possible, pushing each node on the way down.
while node is not None:
stack.append(node)
node = node.left
node = stack.pop()
result.append(node.val) # Its left subtree is done, so visit it.
node = node.right # Then walk its right subtree the same way.
return result
def postorder(node: TreeNode | None, result: list[int]) -> list[int]:
"""Left subtree, then right subtree, then node."""
if node is None:
return result
postorder(node.left, result)
postorder(node.right, result)
result.append(node.val) # Visit the node after both subtrees.
return result
def postorder_iterative(root: TreeNode | None) -> list[int]:
"""Postorder with your own stack, where each node waits until both of its
subtrees are done, just as its recursive call would."""
result: list[int] = []
stack: list[TreeNode] = []
node = root
last: TreeNode | None = None # The node visited most recently.
while node is not None or stack:
# Go left as far as possible, pushing each node on the way down.
while node is not None:
stack.append(node)
node = node.left
top = stack[-1]
# A subtree's root is the last node postorder visits in it, so if the
# right child was just visited, the right subtree is done.
if top.right is not None and top.right is not last:
node = top.right # Walk the right subtree before visiting top.
else:
result.append(top.val) # Both subtrees are done, so visit it.
last = stack.pop()
return result
def level_order(root: TreeNode | None) -> list[list[int]]:
"""Values level by level: top to bottom, and left to right within a level."""
levels: list[list[int]] = []
queue = deque([root] if root is not None else [])
while queue:
# The nodes waiting in the queue right now make up exactly one level.
size = len(queue)
level: list[int] = []
for _ in range(size):
node = queue.popleft()
level.append(node.val)
if node.left is not None:
queue.append(node.left)
if node.right is not None:
queue.append(node.right)
levels.append(level)
return levels
if __name__ == "__main__":
root = build_tree([1, 2, 3, 4, 5, None, 6])
print(preorder(root, [])) # [1, 2, 4, 5, 3, 6]
print(preorder_iterative(root)) # [1, 2, 4, 5, 3, 6]
print(inorder(root, [])) # [4, 2, 5, 1, 3, 6]
print(inorder_iterative(root)) # [4, 2, 5, 1, 3, 6]
print(postorder(root, [])) # [4, 5, 2, 6, 3, 1]
print(postorder_iterative(root)) # [4, 5, 2, 6, 3, 1]
print(level_order(root)) # [[1], [2, 3], [4, 5, 6]]
print(level_order(build_tree([]))) # []class TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
function buildTree(values: (number | null)[]): TreeNode | null {
const first = values.length > 0 ? values[0] : null;
if (first === null) return null;
const root = new TreeNode(first);
const queue = [root];
let head = 0; // Reading from a head index keeps each dequeue O(1).
let i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
const node = queue[head++];
const left = values[i];
if (left !== null) {
node.left = new TreeNode(left);
queue.push(node.left);
}
i++;
const right = i < values.length ? values[i] : null;
if (right !== null) {
node.right = new TreeNode(right);
queue.push(node.right);
}
i++;
}
return root;
}
// Node, then left subtree, then right subtree.
export function preorder(node: TreeNode | null, result: number[]): number[] {
if (node === null) return result;
result.push(node.val); // Visit the node before either subtree.
preorder(node.left, result);
preorder(node.right, result);
return result;
}
// Preorder with your own stack in place of the call stack.
export function preorderIterative(root: TreeNode | null): number[] {
const result: number[] = [];
const stack = root === null ? [] : [root];
while (stack.length > 0) {
const node = stack.pop()!;
result.push(node.val);
// Push the right child first, so the left one comes off the stack first.
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
return result;
}
// Left subtree, then node, then right subtree.
export function inorder(node: TreeNode | null, result: number[]): number[] {
if (node === null) return result;
inorder(node.left, result);
result.push(node.val); // Visit the node between its subtrees.
inorder(node.right, result);
return result;
}
// Inorder with your own stack, where each node waits while its left subtree
// is walked.
export function inorderIterative(root: TreeNode | null): number[] {
const result: number[] = [];
const stack: TreeNode[] = [];
let node = root;
while (node !== null || stack.length > 0) {
// Go left as far as possible, pushing each node on the way down.
while (node !== null) {
stack.push(node);
node = node.left;
}
node = stack.pop()!;
result.push(node.val); // Its left subtree is done, so visit it.
node = node.right; // Then walk its right subtree the same way.
}
return result;
}
// Left subtree, then right subtree, then node.
export function postorder(node: TreeNode | null, result: number[]): number[] {
if (node === null) return result;
postorder(node.left, result);
postorder(node.right, result);
result.push(node.val); // Visit the node after both subtrees.
return result;
}
// Postorder with your own stack, where each node waits until both of its
// subtrees are done, just as its recursive call would.
export function postorderIterative(root: TreeNode | null): number[] {
const result: number[] = [];
const stack: TreeNode[] = [];
let node = root;
let last: TreeNode | null = null; // The node visited most recently.
while (node !== null || stack.length > 0) {
// Go left as far as possible, pushing each node on the way down.
while (node !== null) {
stack.push(node);
node = node.left;
}
const top = stack[stack.length - 1];
// A subtree's root is the last node postorder visits in it, so if the
// right child was just visited, the right subtree is done.
if (top.right !== null && top.right !== last) {
node = top.right; // Walk the right subtree before visiting top.
} else {
result.push(top.val); // Both subtrees are done, so visit it.
last = stack.pop()!;
}
}
return result;
}
// Values level by level: top to bottom, and left to right within a level.
export function levelOrder(root: TreeNode | null): number[][] {
const levels: number[][] = [];
const queue = root === null ? [] : [root];
let head = 0;
while (head < queue.length) {
// The nodes waiting in the queue right now make up exactly one level.
const size = queue.length - head;
const level: number[] = [];
for (let k = 0; k < size; k++) {
const node = queue[head++];
level.push(node.val);
if (node.left !== null) queue.push(node.left);
if (node.right !== null) queue.push(node.right);
}
levels.push(level);
}
return levels;
}
const root = buildTree([1, 2, 3, 4, 5, null, 6]);
console.log(JSON.stringify(preorder(root, []))); // [1,2,4,5,3,6]
console.log(JSON.stringify(preorderIterative(root))); // [1,2,4,5,3,6]
console.log(JSON.stringify(inorder(root, []))); // [4,2,5,1,3,6]
console.log(JSON.stringify(inorderIterative(root))); // [4,2,5,1,3,6]
console.log(JSON.stringify(postorder(root, []))); // [4,5,2,6,3,1]
console.log(JSON.stringify(postorderIterative(root))); // [4,5,2,6,3,1]
console.log(JSON.stringify(levelOrder(root))); // [[1],[2,3],[4,5,6]]
console.log(JSON.stringify(levelOrder(buildTree([])))); // []package main
import "fmt"
// TreeNode is one node of a binary tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// buildTree builds a tree from level-order values, where nil marks a missing
// child. This is how LeetCode writes trees down.
func buildTree(values []any) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}
root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
for i < len(values) {
node := queue[0]
queue = queue[1:]
if values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}
return root
}
// preorder appends values in node, left subtree, right subtree order.
func preorder(node *TreeNode, result []int) []int {
if node == nil {
return result
}
result = append(result, node.Val) // Visit the node before either subtree.
result = preorder(node.Left, result)
result = preorder(node.Right, result)
return result
}
// preorderIterative returns the preorder values using your own stack in place
// of the call stack.
func preorderIterative(root *TreeNode) []int {
result := []int{}
stack := []*TreeNode{}
if root != nil {
stack = append(stack, root)
}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
// Push the right child first, so the left one comes off the stack first.
if node.Right != nil {
stack = append(stack, node.Right)
}
if node.Left != nil {
stack = append(stack, node.Left)
}
}
return result
}
// inorder appends values in left subtree, node, right subtree order.
func inorder(node *TreeNode, result []int) []int {
if node == nil {
return result
}
result = inorder(node.Left, result)
result = append(result, node.Val) // Visit the node between its subtrees.
result = inorder(node.Right, result)
return result
}
// inorderIterative returns the inorder values using your own stack, where each
// node waits while its left subtree is walked.
func inorderIterative(root *TreeNode) []int {
result := []int{}
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
// Go left as far as possible, pushing each node on the way down.
for node != nil {
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val) // Its left subtree is done, so visit it.
node = node.Right // Then walk its right subtree the same way.
}
return result
}
// postorder appends values in left subtree, right subtree, node order.
func postorder(node *TreeNode, result []int) []int {
if node == nil {
return result
}
result = postorder(node.Left, result)
result = postorder(node.Right, result)
result = append(result, node.Val) // Visit the node after both subtrees.
return result
}
// postorderIterative returns the postorder values using your own stack, where
// each node waits until both of its subtrees are done, just as its recursive
// call would.
func postorderIterative(root *TreeNode) []int {
result := []int{}
stack := []*TreeNode{}
node := root
var last *TreeNode // The node visited most recently.
for node != nil || len(stack) > 0 {
// Go left as far as possible, pushing each node on the way down.
for node != nil {
stack = append(stack, node)
node = node.Left
}
top := stack[len(stack)-1]
// A subtree's root is the last node postorder visits in it, so if the
// right child was just visited, the right subtree is done.
if top.Right != nil && top.Right != last {
node = top.Right // Walk the right subtree before visiting top.
} else {
result = append(result, top.Val) // Both subtrees are done, so visit it.
last = top
stack = stack[:len(stack)-1]
}
}
return result
}
// levelOrder returns the values level by level: top to bottom, and left to
// right within a level.
func levelOrder(root *TreeNode) [][]int {
levels := [][]int{}
queue := []*TreeNode{}
if root != nil {
queue = append(queue, root)
}
for len(queue) > 0 {
// The nodes waiting in the queue right now make up exactly one level.
size := len(queue)
level := []int{}
for k := 0; k < size; k++ {
node := queue[0]
queue = queue[1:]
level = append(level, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
levels = append(levels, level)
}
return levels
}
func main() {
root := buildTree([]any{1, 2, 3, 4, 5, nil, 6})
fmt.Println(preorder(root, []int{})) // [1 2 4 5 3 6]
fmt.Println(preorderIterative(root)) // [1 2 4 5 3 6]
fmt.Println(inorder(root, []int{})) // [4 2 5 1 3 6]
fmt.Println(inorderIterative(root)) // [4 2 5 1 3 6]
fmt.Println(postorder(root, []int{})) // [4 5 2 6 3 1]
fmt.Println(postorderIterative(root)) // [4 5 2 6 3 1]
fmt.Println(levelOrder(root)) // [[1] [2 3] [4 5 6]]
fmt.Println(levelOrder(buildTree([]any{}))) // []
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode? BuildTree(int?[] values)
{
if (values.Length == 0 || values[0] is not int first) return null;
var root = new TreeNode(first);
var queue = new Queue<TreeNode>();
queue.Enqueue(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.Length)
{
var node = queue.Dequeue();
if (values[i] is int left)
{
node.Left = new TreeNode(left);
queue.Enqueue(node.Left);
}
i++;
if (i < values.Length && values[i] is int right)
{
node.Right = new TreeNode(right);
queue.Enqueue(node.Right);
}
i++;
}
return root;
}
// Node, then left subtree, then right subtree.
public static List<int> Preorder(TreeNode? node, List<int> result)
{
if (node == null) return result;
result.Add(node.Val); // Visit the node before either subtree.
Preorder(node.Left, result);
Preorder(node.Right, result);
return result;
}
// Preorder with your own stack in place of the call stack.
public static List<int> PreorderIterative(TreeNode? root)
{
var result = new List<int>();
var stack = new Stack<TreeNode>();
if (root != null) stack.Push(root);
while (stack.Count > 0)
{
var node = stack.Pop();
result.Add(node.Val);
// Push the right child first, so the left one comes off the stack first.
if (node.Right != null) stack.Push(node.Right);
if (node.Left != null) stack.Push(node.Left);
}
return result;
}
// Left subtree, then node, then right subtree.
public static List<int> Inorder(TreeNode? node, List<int> result)
{
if (node == null) return result;
Inorder(node.Left, result);
result.Add(node.Val); // Visit the node between its subtrees.
Inorder(node.Right, result);
return result;
}
// Inorder with your own stack, where each node waits while its left subtree
// is walked.
public static List<int> InorderIterative(TreeNode? root)
{
var result = new List<int>();
var stack = new Stack<TreeNode>();
var node = root;
while (node != null || stack.Count > 0)
{
// Go left as far as possible, pushing each node on the way down.
while (node != null)
{
stack.Push(node);
node = node.Left;
}
node = stack.Pop();
result.Add(node.Val); // Its left subtree is done, so visit it.
node = node.Right; // Then walk its right subtree the same way.
}
return result;
}
// Left subtree, then right subtree, then node.
public static List<int> Postorder(TreeNode? node, List<int> result)
{
if (node == null) return result;
Postorder(node.Left, result);
Postorder(node.Right, result);
result.Add(node.Val); // Visit the node after both subtrees.
return result;
}
// Postorder with your own stack, where each node waits until both of its
// subtrees are done, just as its recursive call would.
public static List<int> PostorderIterative(TreeNode? root)
{
var result = new List<int>();
var stack = new Stack<TreeNode>();
var node = root;
TreeNode? last = null; // The node visited most recently.
while (node != null || stack.Count > 0)
{
// Go left as far as possible, pushing each node on the way down.
while (node != null)
{
stack.Push(node);
node = node.Left;
}
var top = stack.Peek();
// A subtree's root is the last node postorder visits in it, so if the
// right child was just visited, the right subtree is done.
if (top.Right != null && top.Right != last)
{
node = top.Right; // Walk the right subtree before visiting top.
}
else
{
result.Add(top.Val); // Both subtrees are done, so visit it.
last = stack.Pop();
}
}
return result;
}
// Values level by level: top to bottom, and left to right within a level.
public static List<List<int>> LevelOrder(TreeNode? root)
{
var levels = new List<List<int>>();
var queue = new Queue<TreeNode>();
if (root != null) queue.Enqueue(root);
while (queue.Count > 0)
{
// The nodes waiting in the queue right now make up exactly one level.
int size = queue.Count;
var level = new List<int>();
for (int k = 0; k < size; k++)
{
var node = queue.Dequeue();
level.Add(node.Val);
if (node.Left != null) queue.Enqueue(node.Left);
if (node.Right != null) queue.Enqueue(node.Right);
}
levels.Add(level);
}
return levels;
}
public static void Main()
{
var root = BuildTree(new int?[] { 1, 2, 3, 4, 5, null, 6 });
Console.WriteLine(Format(Preorder(root, new List<int>()))); // [1, 2, 4, 5, 3, 6]
Console.WriteLine(Format(PreorderIterative(root))); // [1, 2, 4, 5, 3, 6]
Console.WriteLine(Format(Inorder(root, new List<int>()))); // [4, 2, 5, 1, 3, 6]
Console.WriteLine(Format(InorderIterative(root))); // [4, 2, 5, 1, 3, 6]
Console.WriteLine(Format(Postorder(root, new List<int>()))); // [4, 5, 2, 6, 3, 1]
Console.WriteLine(Format(PostorderIterative(root))); // [4, 5, 2, 6, 3, 1]
Console.WriteLine(Format(LevelOrder(root))); // [[1], [2, 3], [4, 5, 6]]
Console.WriteLine(Format(LevelOrder(BuildTree(new int?[] { })))); // []
}
static string Format(List<int> values) => "[" + string.Join(", ", values) + "]";
static string Format(List<List<int>> levels) => "[" + string.Join(", ", levels.Select(Format)) + "]";
}import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode buildTree(Integer[] values) {
if (values.length == 0 || values[0] == null) return null;
TreeNode root = new TreeNode(values[0]);
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
TreeNode node = queue.poll();
if (values[i] != null) {
node.left = new TreeNode(values[i]);
queue.add(node.left);
}
i++;
if (i < values.length && values[i] != null) {
node.right = new TreeNode(values[i]);
queue.add(node.right);
}
i++;
}
return root;
}
// Node, then left subtree, then right subtree.
static List<Integer> preorder(TreeNode node, List<Integer> result) {
if (node == null) return result;
result.add(node.val); // Visit the node before either subtree.
preorder(node.left, result);
preorder(node.right, result);
return result;
}
// Preorder with your own stack in place of the call stack.
static List<Integer> preorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
if (root != null) stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
// Push the right child first, so the left one comes off the stack first.
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
return result;
}
// Left subtree, then node, then right subtree.
static List<Integer> inorder(TreeNode node, List<Integer> result) {
if (node == null) return result;
inorder(node.left, result);
result.add(node.val); // Visit the node between its subtrees.
inorder(node.right, result);
return result;
}
// Inorder with your own stack, where each node waits while its left subtree
// is walked.
static List<Integer> inorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
while (node != null || !stack.isEmpty()) {
// Go left as far as possible, pushing each node on the way down.
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
result.add(node.val); // Its left subtree is done, so visit it.
node = node.right; // Then walk its right subtree the same way.
}
return result;
}
// Left subtree, then right subtree, then node.
static List<Integer> postorder(TreeNode node, List<Integer> result) {
if (node == null) return result;
postorder(node.left, result);
postorder(node.right, result);
result.add(node.val); // Visit the node after both subtrees.
return result;
}
// Postorder with your own stack, where each node waits until both of its
// subtrees are done, just as its recursive call would.
static List<Integer> postorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
TreeNode last = null; // The node visited most recently.
while (node != null || !stack.isEmpty()) {
// Go left as far as possible, pushing each node on the way down.
while (node != null) {
stack.push(node);
node = node.left;
}
TreeNode top = stack.peek();
// A subtree's root is the last node postorder visits in it, so if the
// right child was just visited, the right subtree is done.
if (top.right != null && top.right != last) {
node = top.right; // Walk the right subtree before visiting top.
} else {
result.add(top.val); // Both subtrees are done, so visit it.
last = stack.pop();
}
}
return result;
}
// Values level by level: top to bottom, and left to right within a level.
static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
if (root != null) queue.add(root);
while (!queue.isEmpty()) {
// The nodes waiting in the queue right now make up exactly one level.
int size = queue.size();
List<Integer> level = new ArrayList<>();
for (int k = 0; k < size; k++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
levels.add(level);
}
return levels;
}
public static void main(String[] args) {
TreeNode root = buildTree(new Integer[] {1, 2, 3, 4, 5, null, 6});
System.out.println(preorder(root, new ArrayList<>())); // [1, 2, 4, 5, 3, 6]
System.out.println(preorderIterative(root)); // [1, 2, 4, 5, 3, 6]
System.out.println(inorder(root, new ArrayList<>())); // [4, 2, 5, 1, 3, 6]
System.out.println(inorderIterative(root)); // [4, 2, 5, 1, 3, 6]
System.out.println(postorder(root, new ArrayList<>())); // [4, 5, 2, 6, 3, 1]
System.out.println(postorderIterative(root)); // [4, 5, 2, 6, 3, 1]
System.out.println(levelOrder(root)); // [[1], [2, 3], [4, 5, 6]]
System.out.println(levelOrder(buildTree(new Integer[] {}))); // []
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
Return the length of the longest path between any two nodes in a binary tree, counted in edges. The path can start and end anywhere, and it doesn't have to pass through the root.
Example: [1, 2, 3, 4, 5] returns 3. The path goes from 4 up to 2, up to 1 and down to 3, which is three edges.
Show SolutionHide Solution
Approach
Every path has one highest node, where it stops climbing and turns down. The longest path that turns at a given node runs as deep as possible into its left subtree and as deep as possible into its right. With heights counted in nodes, that path has leftHeight + rightHeight edges, because each node on the way down adds the edge above it.
You don't know which node is the turning point, so check them all in one postorder pass. Each call returns two numbers: the subtree's height, which the parent needs to extend a path upward, and the longest path found anywhere inside the subtree, which may never reach the parent at all. The answer is the second number at the root.
1
/
2
/ \
3 4
/ \
5 6Diameter, Bottom Up
Step 1 of 8
root
- Call combining its children
- Children whose answers it reads
- Returned its height and longest path
- Off the longest path
Each call returns two numbers for its subtree: its height h in nodes, and the longest path d anywhere inside it, in edges. Postorder finishes both children before the node itself.
All 8 steps as text
- Each call returns two numbers for its subtree: its height
hin nodes, and the longest pathdanywhere inside it, in edges. Postorder finishes both children before the node itself. 5is a leaf. Both children are empty and return[0, 0], so a path bending at5has0 + 0 = 0edges. It returnsh=1 d=0.- At
3, the left side is1deep and the right0, so the longest path bending here has1 + 0 = 1edge. That beats the0found below, sod=1. 6is a leaf. Both children are empty and return[0, 0], so a path bending at6has0 + 0 = 0edges. It returnsh=1 d=0.- At
4, the left side is0deep and the right1, so the longest path bending here has0 + 1 = 1edge. That beats the0found below, sod=1. - At
2, the left side is2deep and the right2, so the longest path bending here has2 + 2 = 4edges. That beats the1found below, sod=4. - At
1, the left side is3deep and the right0, so the longest path bending here has3 + 0 = 3edges. The path of 4 edges found below is longer, so it passesd=4up. - The diameter is
4: the path5,3,2,4,6bends at2and never touches the root. That's why every node checks its own bend, not just the root.
Time O(n)Space O(h)
from collections import deque
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def build_tree(values: list[int | None]) -> TreeNode | None:
"""Builds a tree from level-order values, where None marks a missing child.
This is how LeetCode writes trees down."""
first = values[0] if values else None
if first is None:
return None
root = TreeNode(first)
queue = deque([root])
i = 1
# Hand out the remaining values two at a time, left child then right, to
# the nodes in the order they were created.
while i < len(values):
node = queue.popleft()
left = values[i]
if left is not None:
node.left = TreeNode(left)
queue.append(node.left)
i += 1
right = values[i] if i < len(values) else None
if right is not None:
node.right = TreeNode(right)
queue.append(node.right)
i += 1
return root
def height_and_diameter(node: TreeNode | None) -> tuple[int, int]:
"""Height of the subtree at `node` (counted in nodes) and the longest path
anywhere inside it (counted in edges)."""
if node is None:
return 0, 0
left_height, left_diameter = height_and_diameter(node.left)
right_height, right_diameter = height_and_diameter(node.right)
# The longest path that bends here runs left_height edges down the left
# side and right_height edges down the right.
through_node = left_height + right_height
height = 1 + max(left_height, right_height)
return height, max(through_node, left_diameter, right_diameter)
def diameter_of_binary_tree(root: TreeNode | None) -> int:
"""Number of edges on the longest path between any two nodes."""
return height_and_diameter(root)[1]
if __name__ == "__main__":
print(diameter_of_binary_tree(build_tree([1, 2, 3, 4, 5]))) # 3
print(diameter_of_binary_tree(build_tree([1, 2]))) # 1
print(diameter_of_binary_tree(build_tree([1, 2, None, 3, 4, 5, None, None, 6]))) # 4
print(diameter_of_binary_tree(build_tree([1]))) # 0class TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
function buildTree(values: (number | null)[]): TreeNode | null {
const first = values.length > 0 ? values[0] : null;
if (first === null) return null;
const root = new TreeNode(first);
const queue = [root];
let head = 0; // Reading from a head index keeps each dequeue O(1).
let i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
const node = queue[head++];
const left = values[i];
if (left !== null) {
node.left = new TreeNode(left);
queue.push(node.left);
}
i++;
const right = i < values.length ? values[i] : null;
if (right !== null) {
node.right = new TreeNode(right);
queue.push(node.right);
}
i++;
}
return root;
}
// Height of the subtree at `node` (counted in nodes) and the longest path
// anywhere inside it (counted in edges).
function heightAndDiameter(node: TreeNode | null): [number, number] {
if (node === null) return [0, 0];
const [leftHeight, leftDiameter] = heightAndDiameter(node.left);
const [rightHeight, rightDiameter] = heightAndDiameter(node.right);
// The longest path that bends here runs leftHeight edges down the left
// side and rightHeight edges down the right.
const throughNode = leftHeight + rightHeight;
const height = 1 + Math.max(leftHeight, rightHeight);
return [height, Math.max(throughNode, leftDiameter, rightDiameter)];
}
// Number of edges on the longest path between any two nodes.
export function diameterOfBinaryTree(root: TreeNode | null): number {
return heightAndDiameter(root)[1];
}
console.log(diameterOfBinaryTree(buildTree([1, 2, 3, 4, 5]))); // 3
console.log(diameterOfBinaryTree(buildTree([1, 2]))); // 1
console.log(diameterOfBinaryTree(buildTree([1, 2, null, 3, 4, 5, null, null, 6]))); // 4
console.log(diameterOfBinaryTree(buildTree([1]))); // 0package main
import "fmt"
// TreeNode is one node of a binary tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// buildTree builds a tree from level-order values, where nil marks a missing
// child. This is how LeetCode writes trees down.
func buildTree(values []any) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}
root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
for i < len(values) {
node := queue[0]
queue = queue[1:]
if values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}
return root
}
// heightAndDiameter returns the height of the subtree at node (counted in
// nodes) and the longest path anywhere inside it (counted in edges).
func heightAndDiameter(node *TreeNode) (int, int) {
if node == nil {
return 0, 0
}
leftHeight, leftDiameter := heightAndDiameter(node.Left)
rightHeight, rightDiameter := heightAndDiameter(node.Right)
// The longest path that bends here runs leftHeight edges down the left
// side and rightHeight edges down the right.
throughNode := leftHeight + rightHeight
height := 1 + max(leftHeight, rightHeight)
return height, max(throughNode, leftDiameter, rightDiameter)
}
// diameterOfBinaryTree returns the number of edges on the longest path
// between any two nodes.
func diameterOfBinaryTree(root *TreeNode) int {
_, diameter := heightAndDiameter(root)
return diameter
}
func main() {
fmt.Println(diameterOfBinaryTree(buildTree([]any{1, 2, 3, 4, 5}))) // 3
fmt.Println(diameterOfBinaryTree(buildTree([]any{1, 2}))) // 1
fmt.Println(diameterOfBinaryTree(buildTree([]any{1, 2, nil, 3, 4, 5, nil, nil, 6}))) // 4
fmt.Println(diameterOfBinaryTree(buildTree([]any{1}))) // 0
}using System;
using System.Collections.Generic;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode? BuildTree(int?[] values)
{
if (values.Length == 0 || values[0] is not int first) return null;
var root = new TreeNode(first);
var queue = new Queue<TreeNode>();
queue.Enqueue(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.Length)
{
var node = queue.Dequeue();
if (values[i] is int left)
{
node.Left = new TreeNode(left);
queue.Enqueue(node.Left);
}
i++;
if (i < values.Length && values[i] is int right)
{
node.Right = new TreeNode(right);
queue.Enqueue(node.Right);
}
i++;
}
return root;
}
// Height of the subtree at node (counted in nodes) and the longest path
// anywhere inside it (counted in edges).
static (int Height, int Diameter) HeightAndDiameter(TreeNode? node)
{
if (node == null) return (0, 0);
var (leftHeight, leftDiameter) = HeightAndDiameter(node.Left);
var (rightHeight, rightDiameter) = HeightAndDiameter(node.Right);
// The longest path that bends here runs leftHeight edges down the left
// side and rightHeight edges down the right.
int throughNode = leftHeight + rightHeight;
int height = 1 + Math.Max(leftHeight, rightHeight);
return (height, Math.Max(throughNode, Math.Max(leftDiameter, rightDiameter)));
}
// Number of edges on the longest path between any two nodes.
public static int DiameterOfBinaryTree(TreeNode? root) => HeightAndDiameter(root).Diameter;
public static void Main()
{
Console.WriteLine(DiameterOfBinaryTree(BuildTree(new int?[] { 1, 2, 3, 4, 5 }))); // 3
Console.WriteLine(DiameterOfBinaryTree(BuildTree(new int?[] { 1, 2 }))); // 1
Console.WriteLine(DiameterOfBinaryTree(BuildTree(new int?[] { 1, 2, null, 3, 4, 5, null, null, 6 }))); // 4
Console.WriteLine(DiameterOfBinaryTree(BuildTree(new int?[] { 1 }))); // 0
}
}import java.util.ArrayDeque;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode buildTree(Integer[] values) {
if (values.length == 0 || values[0] == null) return null;
TreeNode root = new TreeNode(values[0]);
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
TreeNode node = queue.poll();
if (values[i] != null) {
node.left = new TreeNode(values[i]);
queue.add(node.left);
}
i++;
if (i < values.length && values[i] != null) {
node.right = new TreeNode(values[i]);
queue.add(node.right);
}
i++;
}
return root;
}
// Height of a subtree (counted in nodes) and the longest path anywhere
// inside it (counted in edges).
record Subtree(int height, int diameter) {}
static Subtree heightAndDiameter(TreeNode node) {
if (node == null) return new Subtree(0, 0);
Subtree left = heightAndDiameter(node.left);
Subtree right = heightAndDiameter(node.right);
// The longest path that bends here runs left.height() edges down the left
// side and right.height() edges down the right.
int throughNode = left.height() + right.height();
int height = 1 + Math.max(left.height(), right.height());
return new Subtree(height, Math.max(throughNode, Math.max(left.diameter(), right.diameter())));
}
// Number of edges on the longest path between any two nodes.
static int diameterOfBinaryTree(TreeNode root) {
return heightAndDiameter(root).diameter();
}
public static void main(String[] args) {
System.out.println(diameterOfBinaryTree(buildTree(new Integer[] {1, 2, 3, 4, 5}))); // 3
System.out.println(diameterOfBinaryTree(buildTree(new Integer[] {1, 2}))); // 1
System.out.println(diameterOfBinaryTree(buildTree(new Integer[] {1, 2, null, 3, 4, 5, null, null, 6}))); // 4
System.out.println(diameterOfBinaryTree(buildTree(new Integer[] {1}))); // 0
}
}Problem
Given the root of a binary tree and two of its nodes, p and q, return their lowest common ancestor: the deepest node that has both of them in its subtree. A node counts as part of its own subtree. Values are unique, and both nodes are in the tree.
Example: in the tree [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4] drawn below, the lowest common ancestor of 5 and 1 is 3. For 5 and 4 it's 5 itself, because 4 sits inside 5's subtree.
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4Show SolutionHide Solution
Approach
Ask each subtree one question: does it contain p or q, and if so, which node should it report? An empty subtree reports null. A node that is p or q reports itself straight away, without searching below it. Every other node asks both children and combines their reports:
- If both sides report a node,
pis on one side andqis on the other. Their paths from the root split here, so this node is the answer. - If only one side reports a node, pass it up. Either both targets are down that side and the report is already their ancestor, or it's the only target found so far.
- Stopping at
pwithout looking below it is safe because both nodes are guaranteed to exist. Ifqis underp, thenpis the answer. If it isn't, the search findsqelsewhere and the answer is higher up.
Each node is visited at most once. LeetCode passes the nodes themselves, while the demo passes their values to keep the setup short. The comparison works the same way.
Time O(n)Space O(h)
from collections import deque
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def build_tree(values: list[int | None]) -> TreeNode | None:
"""Builds a tree from level-order values, where None marks a missing child.
This is how LeetCode writes trees down."""
first = values[0] if values else None
if first is None:
return None
root = TreeNode(first)
queue = deque([root])
i = 1
# Hand out the remaining values two at a time, left child then right, to
# the nodes in the order they were created.
while i < len(values):
node = queue.popleft()
left = values[i]
if left is not None:
node.left = TreeNode(left)
queue.append(node.left)
i += 1
right = values[i] if i < len(values) else None
if right is not None:
node.right = TreeNode(right)
queue.append(node.right)
i += 1
return root
def lowest_common_ancestor(node: TreeNode | None, p: int, q: int) -> TreeNode | None:
"""The lowest node with both p and q in its subtree, where a node counts as
part of its own subtree. Assumes both values are in the tree."""
# An empty subtree holds neither. If this node is p or q, it's the answer for
# this subtree, whether or not the other one sits below it.
if node is None or node.val == p or node.val == q:
return node
left = lowest_common_ancestor(node.left, p, q)
right = lowest_common_ancestor(node.right, p, q)
# One on each side: this is where their paths from the root split.
if left is not None and right is not None:
return node
# Otherwise everything found is on one side, so pass that up.
return left if left is not None else right
if __name__ == "__main__":
root = build_tree([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])
print(lowest_common_ancestor(root, 5, 1).val) # 3
print(lowest_common_ancestor(root, 5, 4).val) # 5
print(lowest_common_ancestor(root, 7, 4).val) # 2
print(lowest_common_ancestor(build_tree([1, 2]), 1, 2).val) # 1class TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
function buildTree(values: (number | null)[]): TreeNode | null {
const first = values.length > 0 ? values[0] : null;
if (first === null) return null;
const root = new TreeNode(first);
const queue = [root];
let head = 0; // Reading from a head index keeps each dequeue O(1).
let i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
const node = queue[head++];
const left = values[i];
if (left !== null) {
node.left = new TreeNode(left);
queue.push(node.left);
}
i++;
const right = i < values.length ? values[i] : null;
if (right !== null) {
node.right = new TreeNode(right);
queue.push(node.right);
}
i++;
}
return root;
}
// The lowest node with both p and q in its subtree, where a node counts as
// part of its own subtree. Assumes both values are in the tree.
export function lowestCommonAncestor(node: TreeNode | null, p: number, q: number): TreeNode | null {
// An empty subtree holds neither. If this node is p or q, it's the answer for
// this subtree, whether or not the other one sits below it.
if (node === null || node.val === p || node.val === q) return node;
const left = lowestCommonAncestor(node.left, p, q);
const right = lowestCommonAncestor(node.right, p, q);
// One on each side: this is where their paths from the root split.
if (left !== null && right !== null) return node;
// Otherwise everything found is on one side, so pass that up.
return left !== null ? left : right;
}
const root = buildTree([3, 5, 1, 6, 2, 0, 8, null, null, 7, 4]);
console.log(lowestCommonAncestor(root, 5, 1)?.val); // 3
console.log(lowestCommonAncestor(root, 5, 4)?.val); // 5
console.log(lowestCommonAncestor(root, 7, 4)?.val); // 2
console.log(lowestCommonAncestor(buildTree([1, 2]), 1, 2)?.val); // 1package main
import "fmt"
// TreeNode is one node of a binary tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// buildTree builds a tree from level-order values, where nil marks a missing
// child. This is how LeetCode writes trees down.
func buildTree(values []any) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}
root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
for i < len(values) {
node := queue[0]
queue = queue[1:]
if values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}
return root
}
// lowestCommonAncestor returns the lowest node with both p and q in its
// subtree, where a node counts as part of its own subtree. It assumes both
// values are in the tree.
func lowestCommonAncestor(node *TreeNode, p, q int) *TreeNode {
// An empty subtree holds neither. If this node is p or q, it's the answer for
// this subtree, whether or not the other one sits below it.
if node == nil || node.Val == p || node.Val == q {
return node
}
left := lowestCommonAncestor(node.Left, p, q)
right := lowestCommonAncestor(node.Right, p, q)
// One on each side: this is where their paths from the root split.
if left != nil && right != nil {
return node
}
// Otherwise everything found is on one side, so pass that up.
if left != nil {
return left
}
return right
}
func main() {
root := buildTree([]any{3, 5, 1, 6, 2, 0, 8, nil, nil, 7, 4})
fmt.Println(lowestCommonAncestor(root, 5, 1).Val) // 3
fmt.Println(lowestCommonAncestor(root, 5, 4).Val) // 5
fmt.Println(lowestCommonAncestor(root, 7, 4).Val) // 2
fmt.Println(lowestCommonAncestor(buildTree([]any{1, 2}), 1, 2).Val) // 1
}using System;
using System.Collections.Generic;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode? BuildTree(int?[] values)
{
if (values.Length == 0 || values[0] is not int first) return null;
var root = new TreeNode(first);
var queue = new Queue<TreeNode>();
queue.Enqueue(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.Length)
{
var node = queue.Dequeue();
if (values[i] is int left)
{
node.Left = new TreeNode(left);
queue.Enqueue(node.Left);
}
i++;
if (i < values.Length && values[i] is int right)
{
node.Right = new TreeNode(right);
queue.Enqueue(node.Right);
}
i++;
}
return root;
}
// The lowest node with both p and q in its subtree, where a node counts as
// part of its own subtree. Assumes both values are in the tree.
public static TreeNode? LowestCommonAncestor(TreeNode? node, int p, int q)
{
// An empty subtree holds neither. If this node is p or q, it's the answer for
// this subtree, whether or not the other one sits below it.
if (node == null || node.Val == p || node.Val == q) return node;
var left = LowestCommonAncestor(node.Left, p, q);
var right = LowestCommonAncestor(node.Right, p, q);
// One on each side: this is where their paths from the root split.
if (left != null && right != null) return node;
// Otherwise everything found is on one side, so pass that up.
return left != null ? left : right;
}
public static void Main()
{
var root = BuildTree(new int?[] { 3, 5, 1, 6, 2, 0, 8, null, null, 7, 4 });
Console.WriteLine(LowestCommonAncestor(root, 5, 1)?.Val); // 3
Console.WriteLine(LowestCommonAncestor(root, 5, 4)?.Val); // 5
Console.WriteLine(LowestCommonAncestor(root, 7, 4)?.Val); // 2
Console.WriteLine(LowestCommonAncestor(BuildTree(new int?[] { 1, 2 }), 1, 2)?.Val); // 1
}
}import java.util.ArrayDeque;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode buildTree(Integer[] values) {
if (values.length == 0 || values[0] == null) return null;
TreeNode root = new TreeNode(values[0]);
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
TreeNode node = queue.poll();
if (values[i] != null) {
node.left = new TreeNode(values[i]);
queue.add(node.left);
}
i++;
if (i < values.length && values[i] != null) {
node.right = new TreeNode(values[i]);
queue.add(node.right);
}
i++;
}
return root;
}
// The lowest node with both p and q in its subtree, where a node counts as
// part of its own subtree. Assumes both values are in the tree.
static TreeNode lowestCommonAncestor(TreeNode node, int p, int q) {
// An empty subtree holds neither. If this node is p or q, it's the answer for
// this subtree, whether or not the other one sits below it.
if (node == null || node.val == p || node.val == q) return node;
TreeNode left = lowestCommonAncestor(node.left, p, q);
TreeNode right = lowestCommonAncestor(node.right, p, q);
// One on each side: this is where their paths from the root split.
if (left != null && right != null) return node;
// Otherwise everything found is on one side, so pass that up.
return left != null ? left : right;
}
public static void main(String[] args) {
TreeNode root = buildTree(new Integer[] {3, 5, 1, 6, 2, 0, 8, null, null, 7, 4});
System.out.println(lowestCommonAncestor(root, 5, 1).val); // 3
System.out.println(lowestCommonAncestor(root, 5, 4).val); // 5
System.out.println(lowestCommonAncestor(root, 7, 4).val); // 2
System.out.println(lowestCommonAncestor(buildTree(new Integer[] {1, 2}), 1, 2).val); // 1
}
}Common Pitfalls
- Forgetting the
nullbase case, or readingnode.left.valbefore checking thatnode.leftexists. - Mixing up height counted in nodes with height counted in edges. Diameter counts edges, while LeetCode's Maximum Depth counts nodes. Decide which one your helper returns and stick with it.
- Assuming the answer passes through the root. A diameter or a maximum path sum can sit entirely inside one subtree, so check for the best answer at every node.
- Building lists by joining the children's lists at every node, as in
[node.val, ...left, ...right]. On a skewed tree that copies values over and over and costs O(n²). Pass one list down and append to it. - Dequeuing with
shift()in JavaScript orpop(0)in Python. Both move every remaining element on each call. Use a head index orcollections.deque.
Practice
More problems to try on your own.