DSA sheet · Trees · BFS pattern
Vertical Order Traversal
Top View kept the highest node of each column and Bottom View kept the lowest. This problem wants every node of every column, in a strict order. The teacher's plan has three stages, and they're the same for DFS and BFS: (1) walk the tree and write down (column, row, value) for every node, (2) sort that list, (3) cut the sorted list into one group per column. DFS and BFS only differ in stage 1, so she solves it with DFS first, then shows the BFS way to fill the same list.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Columns and rows (horizontal distance and level)
- Part A · Vertical Order with DFS
- Part B · Vertical Order with BFS
- Part C · Revision page
Part 0 · Before starting: every node gets (row, column)
| word | meaning | rule |
|---|---|---|
| row = level | how far down the node is | root 0, every child = parent + 1 |
| column = horizontal distance (HD), "vertex" in the video | how far left/right of the root the node is | root 0, left child = parent − 1, right child = parent + 1 |
Picture the x-axis again: the root stands on 0, everything to the left is negative, everything to the right is positive.
left ← -2 -1 0 +1 +2 → right
The teacher's main tree
Each node is written as value (row, col).
1 (0,0)
/ \
6 (1,-1) 2 (1,+1)
/ \ / \
4 (2,-2) 5 (2,0) 3 (2,0) 8 (2,+2)
/ \
7 (3,-1) 9 (3,+1)
- 1 is the root → row 0, column 0.
- 6 = left of 1 → row 1, column 0 − 1 = −1. 2 = right of 1 → row 1, column +1.
- 4 = left of 6 → row 2, column −2. 5 = right of 6 → row 2, column −1 + 1 = 0.
- 3 = left of 2 → row 2, column +1 − 1 = 0. 8 = right of 2 → row 2, column +2.
- 7 = left of 5 → row 3, column −1. 9 = right of 5 → row 3, column +1.
| row ↓ / col → | −2 | −1 | 0 | +1 | +2 |
|---|---|---|---|---|---|
| 0 | 1 | ||||
| 1 | 6 | 2 | |||
| 2 | 4 | 3, 5 (same spot!) | 8 | ||
| 3 | 7 | 9 |
Reading each column top to bottom gives [[4], [6, 7], [1, 3, 5], [2, 9], [8]]. Notice 5 and 3: they are both in row 2, column 0. The problem says that in that case we sort them by value, so 3 comes before 5, even though 5 is in the left subtree.
Example 1 from the problem
3 (0,0)
/ \
9 (1,-1) 20 (1,+1)
/ \
15 (2,0) 7 (2,+2)
Column −1: [9]. Column 0: [3, 15]. Column +1: [20]. Column +2: [7]. Answer [[9], [3, 15], [20], [7]].
Part A · Vertical Order with DFS
LeetCode 987 · Vertical Order Traversal of a Binary Tree
1The question in simple words
Give each node its (row, column) as in Part 0. Return a list of columns, from the leftmost column to the rightmost. Inside each column, list the nodes from top to bottom (smaller row first). If two nodes are in the same row and same column, put the smaller value first.
2What the constraints tell us
- Number of nodes: the teacher reads it as 0 to 1000. If 0 is allowed, BFS needs an explicit empty-tree check at the start. DFS needs its
Nonebase case anyway, because that's what ends the recursion. - n ≤ 1000 is small. Sorting all n entries (n log n) is easily fast enough.
3Intuition: write it all down, then sort
Unlike Top/Bottom View, we can't keep "one node per column". We need all of them, and in a specific order that depends on three things. So instead of being clever while walking, the teacher's idea is:
- Walk the tree in any order and write every node down as a small record (column, row, value).
- Sort the records. The sort puts them in exactly the order we need.
- Walk the sorted records and start a new group whenever the column changes.
Once you have the records, the traversal order no longer matters. That's why DFS and BFS both work equally well here.
4Building the sort order from the example
Sort key 1: column
We want column −2 first, then −1, 0, +1, +2. So the records must be sorted by column first, smallest first. That's why the column goes in position 0 of each record.
Sort key 2: row
Column −1 has 6 (row 1) and 7 (row 3). We read top to bottom, so 6 must come before 7. So when the columns are equal, sort by row, smallest first.
→ Because DFS doesn't always visit higher nodes first. Look at column +1: DFS reaches 9 (row 3, in the left subtree) before 2 (row 1, in the right subtree). Without the row key, 9 could come before 2. Sorting by row fixes this.
Sort key 3: value
Column 0 has 1 (row 0), 5 (row 2), 3 (row 2). 1 comes first by row. 5 and 3 are tied on both column and row, so the problem says sort by value → 3, then 5.
In Python a tuple
(col, row, val) sorts in exactly this order by itself: it compares the first item, and only on a tie looks at the second, then the third. So a plain nodes.sort() is enough.a[0] - b[0], then a[1] - b[1], then a[2] - b[2]). Where did that go?→ That is Java's way of saying "compare column, then row, then value". Python tuples already compare item by item in that order, so the comparator is built in. The teacher also mentions that Python doesn't need a Pair class, since tuples can hold several things.
Stage 3: cutting the sorted list into columns
After sorting, the records for one column are all next to each other. But we don't know in advance how many records each column has. The teacher's trick: remember the previous column.
- Start with
prev_col = -infinity, a value no real column can equal. - For each record: if its column is different from
prev_col, a new column has begun → append a new empty list to the answer and setprev_colto this column. - Either way, add the value to the last list in the answer (
ans[-1], which is Java'sans.get(ans.size() - 1)).
prev_col at minus infinity and not 0?→ The first record must always start a new list. If
prev_col started at 0 and the first column happened to be 0 (a tree with no left child), we'd try to append to a list that doesn't exist. Minus infinity can never match a real column, so the first record always opens a list.5Approach steps
- Make an empty list
nodes. - DFS with
(node, row, col): if None return; else append(col, row, node.val), then go left with(row + 1, col − 1)and right with(row + 1, col + 1). - Sort
nodes(column, then row, then value). - Walk the sorted list; open a new sublist when the column changes; append the value to the last sublist.
- Return the list of sublists.
6Code (Python)
class Solution:
def verticalTraversal(self, root):
nodes = [] # records (col, row, val)
def dfs(node, row, col):
if node is None: # base case
return
nodes.append((col, row, node.val))
dfs(node.left, row + 1, col - 1) # left: one row down, one column left
dfs(node.right, row + 1, col + 1) # right: one row down, one column right
dfs(root, 0, 0)
nodes.sort() # column, then row, then value
ans = []
prev_col = float('-inf')
for col, row, val in nodes:
if col != prev_col: # a new column starts here
ans.append([])
prev_col = col
ans[-1].append(val) # add to the current (last) column
return ans7Code line by line
| line | what it means |
|---|---|
| nodes = [] | The big list of records, one per node. The teacher's "outer list". |
| if node is None: return | Base case. Stops the recursion, and also handles an empty tree. |
| nodes.append((col, row, node.val)) | Column first, row second, value third. The order matters, because that's the order the sort will use. |
| dfs(node.left, row + 1, col - 1) | Left child: one row down, one column left. |
| dfs(node.right, row + 1, col + 1) | Right child: one row down, one column right. |
| nodes.sort() | Tuples sort by column, then row, then value. That's all three rules of the problem in one line. |
| prev_col = float('-inf') | "No column seen yet". Guaranteed to differ from the first real column. |
| if col != prev_col: ans.append([]) prev_col = col | A new column begins: open a new sublist and remember this column. |
| ans[-1].append(val) | Put the value into the sublist of the current column (always the last one). |
8Dry run
Stage 1: DFS fills the list
- 1(row 0, col 0) → add (0, 0, 1). Go left.
- 6(1, −1) → add (−1, 1, 6). Go left.
- 4(2, −2) → add (−2, 2, 4). Both children None → return to 6.
- 6 goes right: 5(2, 0) → add (0, 2, 5). Go left.
- 7(3, −1) → add (−1, 3, 7). Return. Then 9(3, +1) → add (1, 3, 9). Return.
- 5 returns, 6 returns. 1 goes right: 2(1, +1) → add (1, 1, 2).
- 3(2, 0) → add (0, 2, 3). 8(2, +2) → add (2, 2, 8). Everything returns.
Stage 2: sort
| DFS order (before sort) | after sort() | why it's there |
|---|---|---|
| (0, 0, 1) | (-2, 2, 4) | smallest column |
| (-1, 1, 6) | (-1, 1, 6) | column −1, row 1 |
| (-2, 2, 4) | (-1, 3, 7) | column −1, row 3 (below 6) |
| (0, 2, 5) | (0, 0, 1) | column 0, row 0 |
| (-1, 3, 7) | (0, 2, 3) | column 0, row 2, value 3 < 5 |
| (1, 3, 9) | (0, 2, 5) | same column and row as 3, bigger value |
| (1, 1, 2) | (1, 1, 2) | column +1, row 1 |
| (0, 2, 3) | (1, 3, 9) | column +1, row 3: the row key moved it below 2 |
| (2, 2, 8) | (2, 2, 8) | biggest column |
Stage 3: group by column
| record | col vs prev_col | action | ans after |
|---|---|---|---|
| (-2,2,4) | −2 vs −∞ → new | open list, add 4 | [[4]] |
| (-1,1,6) | −1 vs −2 → new | open list, add 6 | [[4],[6]] |
| (-1,3,7) | −1 vs −1 → same | add 7 to last | [[4],[6,7]] |
| (0,0,1) | 0 vs −1 → new | open list, add 1 | … [1]] |
| (0,2,3) | same | add 3 | … [1,3]] |
| (0,2,5) | same | add 5 | … [1,3,5]] |
| (1,1,2) | 1 vs 0 → new | open list, add 2 | … [2]] |
| (1,3,9) | same | add 9 | … [2,9]] |
| (2,2,8) | 2 vs 1 → new | open list, add 8 | … [8]] |
Final: [[4], [6, 7], [1, 3, 5], [2, 9], [8]] ✓
9Complexity & remember
- Time O(n log n). The DFS itself is O(n), one visit per node. But the list has n records, and sorting n records costs n log n. The bigger term wins. (The teacher first says O(n), then corrects herself in the BFS section: it's the sort that makes it n log n.)
- Space: the recursion stack holds one node per level of the current path, so about log n for a complete tree and n for a skewed tree. On top of that, the records list always has n entries, so the total extra space is O(n).
Part B · Vertical Order with BFS
Stages 2 and 3 (sort and group) are exactly the same as Part A. Only stage 1, filling the records list, changes: we use a queue instead of recursion.
1The question in simple words
Same as Part A.
2What the constraints tell us
- If the tree can have 0 nodes, BFS needs
if root is None: return []at the top. Otherwise we'd pushNoneinto the queue and crash when reading.left.
3Intuition
Normal level order traversal, but each queue entry carries three things: the node, its row and its column. When we pop an entry, we write its record, then push its children with their new row and column.
→ It's not strictly needed for ordering rows, since BFS appends rows in order. But keep it anyway: it keeps the sort identical to the DFS version, and records from the same row and column still need the value tie-break. With
(col, row, val) one sort() handles everything.4The changes from DFS
- The queue holds tuples
(node, row, col). The teacher needs a Pair class with three fields in Java. Python doesn't. - Push a child only if it exists: left →
(row + 1, col − 1), right →(row + 1, col + 1).
5Approach steps
- If root is None, return [].
- Queue starts with
(root, 0, 0). - While the queue isn't empty: pop, append
(col, row, val)tonodes, push existing children. - Sort and group exactly as in Part A.
6Code (Python)
from collections import deque
class Solution:
def verticalTraversal(self, root):
if root is None: # needed in BFS if 0 nodes allowed
return []
nodes = [] # records (col, row, val)
queue = deque()
queue.append((root, 0, 0)) # (node, row, col)
while queue:
node, row, col = queue.popleft()
nodes.append((col, row, node.val))
if node.left:
queue.append((node.left, row + 1, col - 1))
if node.right:
queue.append((node.right, row + 1, col + 1))
nodes.sort() # same as DFS from here on
ans = []
prev_col = float('-inf')
for col, row, val in nodes:
if col != prev_col:
ans.append([])
prev_col = col
ans[-1].append(val)
return ans7Code line by line (only the new lines)
| line | what it means |
|---|---|
| queue.append((root, 0, 0)) | The root is at row 0, column 0. |
| node, row, col = queue.popleft() | Unpack all three things at once. |
| nodes.append((col, row, node.val)) | Same record shape as DFS: column first. |
| queue.append((node.left, row + 1, col - 1)) | Left child: one row down, one column left. |
| queue.append((node.right, row + 1, col + 1)) | Right child: one row down, one column right. |
8Dry run: the queue
Main tree. Boxes show value(row,col).
Notice that BFS added 5 before 3 (5 is further left in row 2). The value key in the sort swaps them back into 3, 5.
9Complexity & remember
- Time O(n log n): O(n) for BFS, n log n for sorting n records.
- Space O(n): the queue (up to a whole level) plus the n records.
(node, row, col). Sort and group are copied unchanged.Part C · Revision page
| DFS | BFS | |
|---|---|---|
| fills the list by | recursion with (node, row, col) | queue of (node, row, col) |
| empty tree | base case handles it | explicit check at the top |
| record shape | (col, row, val), column first | |
| sort | nodes.sort(): column ↑, row ↑, value ↑ | |
| grouping | prev_col = -inf; new sublist when column changes; add to ans[-1] | |
| time | O(n) walk + O(n log n) sort = O(n log n) | |
| space | O(n) records + O(height) stack | O(n) records + O(width) queue |
| Top View | Bottom View | Vertical Order | |
|---|---|---|---|
| keeps per column | highest node | lowest node | all nodes |
| needs sorting of nodes? | no | no | yes (row, then value) |
| time | O(n) | O(n) | O(n log n) |
2. Write a record
(col, row, val) for every node, by DFS or BFS.3. One
sort() gives column, then row, then value order.4. Start a new sublist whenever the column changes (
prev_col starts at −∞).5. The sort makes it O(n log n).
✗ forgetting the value tie-break: 5 and 3 come out as [1, 5, 3]
✗ starting
prev_col at 0 (the first column might be 0)✗ BFS without the empty-tree check
✗ saying O(n): the sort makes it O(n log n)
t = TreeNode(1,
TreeNode(6, TreeNode(4), TreeNode(5, TreeNode(7), TreeNode(9))),
TreeNode(2, TreeNode(3), TreeNode(8)))
ex1 = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
s = Solution()
print(s.verticalTraversal(t)) # [[4], [6, 7], [1, 3, 5], [2, 9], [8]]
print(s.verticalTraversal(ex1)) # [[9], [3, 15], [20], [7]]Based on this video: Vertical Order Traversal | BFS & DFS