Diagonal Traversal of a Binary Tree asks you to read a tree along its top-right to bottom-left diagonals. The trick is a single counter, the diagonal index d, that you carry as you walk the tree — and a map that groups nodes by it.
Problem. Given the root of a binary tree, return its nodes grouped by diagonal. Two nodes are on the same diagonal if you can reach one from the other by only following right edges. Moving to a left child starts a new, deeper diagonal.
Example: for the tree below the answer is [[8, 10, 14], [3, 6, 7], [1, 4]].
8
/ \
3 10
/ \ \
1 6 14
/ \
4 7The slow way first
You could try to compute, for each node, a pixel-style diagonal coordinate by walking down from the root every time, or sort nodes by some geometric x-position. That is fiddly and easy to get wrong — diagonals are not about screen coordinates, they are about how you got to the node.
The question to ask: what single piece of information decides a node's diagonal? It is simply how many times you turned left on the path from the root. Every right turn is free; every left turn costs one. So we just need to carry that count.
The idea: carry a diagonal index d
Give the root d = 0. As you move down the tree, update d with one rule:
- go to the right child → keep the same
d - go to the left child → use
d + 1
For every node you visit, append its value to diag[d] in a map. When the walk finishes, read the map out in increasing order of d and concatenate — that is the diagonal traversal.
The key insight: the diagonal index is inherited and adjusted as you descend. You never recompute it from scratch — each child gets its parent's d, plus 1 if it was a left step.
Walk through it
Step through the animation. We start at the root 8 with d = 0. Following right edges keeps us on diagonal 0, so 8, 10, and 14 all land there. Each left step opens a new diagonal: 8 to 3 reaches d = 1, and from 3 the right child 6 and its right child 7 stay on diagonal 1. Two more left steps (3 to 1, 6 to 4) fill diagonal 2. Finally we read the map by increasing d.
Pseudocode
make an empty map "diag" # d -> list of node values
queue = [(root, 0)] # carry (node, diagonal index)
while the queue is not empty:
node, d = pop from the queue
if node is empty: skip it
append node.value to diag[d]
push (node.left, d + 1) # left -> deeper diagonal
push (node.right, d) # right -> same diagonal
read the diagonals in order of d and join themThe Python solution
from collections import defaultdict
def diagonal(root):
diag = defaultdict(list)
queue = [(root, 0)]
while queue:
node, d = queue.pop(0)
if node is None:
continue
diag[d].append(node.val)
queue.append((node.left, d + 1))
queue.append((node.right, d))
result = []
for d in sorted(diag):
result.extend(diag[d])
return resultdiagis adefaultdict(list)mapping a diagonal index → the list of values on that diagonal.- The queue holds pairs
(node, d)so each node travels with its own diagonal index. - Lines 10–12 are the heart: record the current node under
diag[d], then enqueue its children — the left child withd + 1, the right child with the samed. - The
if node is Nonecheck lets us blindly enqueue both children and skip the empty ones, which keeps the loop simple. - At the end we walk the keys in
sortedorder and flatten — diagonal 0 first, then 1, then 2.
Complexity
| Case | Time | Notes |
|---|---|---|
| Visit every node once | O(n) (moderate) | one enqueue + dequeue each |
| Sort the diagonal keys | O(h log h) (moderate) | h diagonals, usually tiny |
O(n) (moderate)We touch each of the n nodes a constant number of times, and the map plus queue hold at most O(n) entries. The only extra cost is sorting the handful of diagonal keys, which is negligible next to the traversal itself.
When this pattern shows up
Whenever a tree problem groups nodes by some path-derived coordinate — vertical order, diagonal, level, or distance — carry that coordinate alongside the node in your traversal and bucket into a map keyed by it. Vertical Order Traversal, Top View, Bottom View, and this diagonal problem are all the same move: descend, adjust a coordinate, group by it.
Watch the direction rule: it is the left child that increases d, not the right. Flip them and you
will traverse anti-diagonals instead. Also remember to read the map in sorted key order — dictionary
insertion order is not guaranteed to match increasing d.
Practice
In the example tree, node 7 is the right child of 6, which is the right child of 3, which is the left child of 8. What diagonal index d does 7 end up on?
1. How does the diagonal index d change when you move to a node child?
2. What does the map diag store?
3. Why do we read the map with sorted(diag) at the end?
4. What is the overall time complexity of this traversal?