Binary tree depth and level order
Recursion for depth, a slice as a queue for level order. The two shapes every tree question uses.
- Write maxDepth with recursion and level order with a slice queue.
- Explain why len(queue) is read before each level's loop.
- Predict the queue contents after each round of BFS.
The problem
Given a binary tree, return its maximum depth (the number of nodes on the longest path from the root down), and its values level by level from top to bottom.
Brute force first
For level order without a queue: find the height, then for each level d walk the whole tree recursively and collect the nodes at depth d.
Each walk is O(n) and there are h levels: O(n·h) time, O(n²) for a tree that is one long line. The queue does it in one pass.
The approach
Depth is recursion: a nil tree has depth 0, any other is 1 + max(left, right). O(n) time, O(h) stack where h is the height.
Level order is breadth-first search. Put the root in a queue. Each round, take everything currently in the queue as one level and add their children to the back. O(n) time, O(width) space.
package mainimport "fmt"type Tree struct { Val int Left, Right *Tree}// maxDepth is the classic recursion: a nil tree has depth 0.func maxDepth(t *Tree) int { if t == nil { return 0 } return 1 + max(maxDepth(t.Left), maxDepth(t.Right))}// levels is breadth-first search with a slice as the queue.func levels(root *Tree) [][]int { var out [][]int if root == nil { return out } queue := []*Tree{root} for len(queue) > 0 { n := len(queue) // everything in the queue now is one level var level []int for _, t := range queue[:n] { level = append(level, t.Val) if t.Left != nil { queue = append(queue, t.Left) } if t.Right != nil { queue = append(queue, t.Right) } } queue = queue[n:] // drop the level we just finished out = append(out, level) } return out}func main() { // 3 // / \ // 9 20 // / \ // 15 7 root := &Tree{3, &Tree{Val: 9}, &Tree{20, &Tree{Val: 15}, &Tree{Val: 7}}} fmt.Println(maxDepth(root)) // 3 fmt.Println(levels(root)) // [[3] [9 20] [15 7]] fmt.Println(maxDepth(nil), levels(nil)) // 0 []}Dry run
The tree 3, children 9 and 20, and 20 has children 15 and 7. Level order:
| round | n = len(queue) | level taken | queue after |
|---|---|---|---|
| 1 | 1 | [3] | [9 20] |
| 2 | 2 | [9 20] | [15 7] |
| 3 | 2 | [15 7] | [] |
Go-specific gotchas
- A slice is a fine queue.
appendto the back,queue = queue[n:]to drop the front. The backing array is reused or reallocated byappend, so this stays O(n) overall. One catch:queue = queue[1:](or[n:]) keeps the old front of the backing array alive untilappendreallocates. For a long-lived or very long queue, keep an indexheadand readqueue[head], or copy the live part into a new slice now and then. - Read `len(queue)` before the inner loop. The loop appends children, and you only want the nodes that were there when the level started.
- **A nil
*Treeis a valid empty tree,** somaxDepth(nil)returns 0 with no special case. maxis a built-in since Go 1.21. You do not needmath.Max, which works onfloat64anyway.
- Depth: recursion, nil is 0.
- Level order: BFS with a slice queue, one level per round.
- Snapshot the queue length before you start a level.
levels(root *Tree) [][]int that returns the values level by level, left to right (breadth-first search). An empty tree returns an empty result. Example: 3 with children 9 and 20, and 20 with children 15 and 7 → [[3] [9 20] [15 7]].Tab indents. Press Esc, then Tab, to leave the editor.
- 1The main task: Return the right side view of a tree: the last value of each level. Reuse the level-order code.
- 2Using the dry-run tree, trace
maxDepth. What does each call return? - 3Return the average value of each level (
[3 14.5 11]for the dry-run tree).