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 · Before starting: every node gets (row, column)

wordmeaningrule
row = levelhow far down the node isroot 0, every child = parent + 1
column = horizontal distance (HD), "vertex" in the videohow far left/right of the root the node isroot 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. 1 is the root → row 0, column 0.
  2. 6 = left of 1 → row 1, column 0 − 1 = −1. 2 = right of 1 → row 1, column +1.
  3. 4 = left of 6 → row 2, column −2. 5 = right of 6 → row 2, column −1 + 1 = 0.
  4. 3 = left of 2 → row 2, column +1 − 1 = 0. 8 = right of 2 → row 2, column +2.
  5. 7 = left of 5 → row 3, column −1. 9 = right of 5 → row 3, column +1.
row ↓ / col →−2−10+1+2
01
162
243, 5 (same spot!)8
379

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

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:

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.

Doubt: DFS visited 6 before 7 anyway. Why do we need the row in the sort?
→ 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.

Sort ordercolumn ↑, then row ↑, then value ↑.
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.
Doubt: the teacher writes a comparator (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.

Doubt: why start 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

  1. Make an empty list nodes.
  2. 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).
  3. Sort nodes (column, then row, then value).
  4. Walk the sorted list; open a new sublist when the column changes; append the value to the last sublist.
  5. Return the list of sublists.

6Code (Python)

Vertical Order with DFS
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 ans

7Code line by line

linewhat it means
nodes = []The big list of records, one per node. The teacher's "outer list".
if node is None: returnBase 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 = colA 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. 1(row 0, col 0) → add (0, 0, 1). Go left.
  2. 6(1, −1) → add (−1, 1, 6). Go left.
  3. 4(2, −2) → add (−2, 2, 4). Both children None → return to 6.
  4. 6 goes right: 5(2, 0) → add (0, 2, 5). Go left.
  5. 7(3, −1) → add (−1, 3, 7). Return. Then 9(3, +1) → add (1, 3, 9). Return.
  6. 5 returns, 6 returns. 1 goes right: 2(1, +1) → add (1, 1, 2).
  7. 3(2, 0) → add (0, 2, 3). 8(2, +2) → add (2, 2, 8). Everything returns.
stack when 7 is added
1(0,0)6(1,−1)5(2,0)7(3,−1)
stack when 3 is added
1(0,0)2(1,+1)3(2,0)

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

recordcol vs prev_colactionans after
(-2,2,4)−2 vs −∞ → newopen list, add 4[[4]]
(-1,1,6)−1 vs −2 → newopen list, add 6[[4],[6]]
(-1,3,7)−1 vs −1 → sameadd 7 to last[[4],[6,7]]
(0,0,1)0 vs −1 → newopen list, add 1… [1]]
(0,2,3)sameadd 3… [1,3]]
(0,2,5)sameadd 5… [1,3,5]]
(1,1,2)1 vs 0 → newopen list, add 2… [2]]
(1,3,9)sameadd 9… [2,9]]
(2,2,8)2 vs 1 → newopen list, add 8… [8]]

Final: [[4], [6, 7], [1, 3, 5], [2, 9], [8]] ✓

9Complexity & remember

Remember (DFS)Record (col, row, val) for every node → sort → open a new sublist whenever the column changes. Time is n log n because of the sort.

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

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.

Doubt: BFS already goes top to bottom. Do we still need the row in the sort?
→ 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

5Approach steps

  1. If root is None, return [].
  2. Queue starts with (root, 0, 0).
  3. While the queue isn't empty: pop, append (col, row, val) to nodes, push existing children.
  4. Sort and group exactly as in Part A.

6Code (Python)

Vertical Order with BFS
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 ans

7Code line by line (only the new lines)

linewhat 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).

start1(0,0)
pop 1record (0,0,1); push 6(1,−1), 2(1,+1)
6(1,-1)2(1,+1)
pop 6record (−1,1,6); push 4(2,−2), 5(2,0)
pop 2record (1,1,2); push 3(2,0), 8(2,+2)
4(2,-2)5(2,0)3(2,0)8(2,+2)
pop 4, 5records (−2,2,4), (0,2,5); 5 pushes 7(3,−1), 9(3,+1)
pop 3, 8records (0,2,3), (2,2,8)
7(3,-1)9(3,+1)
pop 7, 9records (−1,3,7), (1,3,9) → queue empty
sort(−2,2,4) (−1,1,6) (−1,3,7) (0,0,1) (0,2,3) (0,2,5) (1,1,2) (1,3,9) (2,2,8)
group[[4], [6, 7], [1, 3, 5], [2, 9], [8]] ✓

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

Remember (BFS)DFS and BFS are just two ways to fill the same list. Queue entries are (node, row, col). Sort and group are copied unchanged.

Part C · Revision page

DFSBFS
fills the list byrecursion with (node, row, col)queue of (node, row, col)
empty treebase case handles itexplicit check at the top
record shape(col, row, val), column first
sortnodes.sort(): column ↑, row ↑, value ↑
groupingprev_col = -inf; new sublist when column changes; add to ans[-1]
timeO(n) walk + O(n log n) sort = O(n log n)
spaceO(n) records + O(height) stackO(n) records + O(width) queue
Top ViewBottom ViewVertical Order
keeps per columnhighest nodelowest nodeall nodes
needs sorting of nodes?nonoyes (row, then value)
timeO(n)O(n)O(n log n)
If you remember only 5 lines 1. Row = level (+1 per step down). Column = HD (−1 left, +1 right).
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).
Mistakes to avoid ✗ putting row or value first in the record (the sort order breaks)
✗ 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)
test it yourself (paste under either solution above)
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