314. Binary Tree Vertical Order Traversal - Explanation

Problem Link

Description

You are given the root node of a binary tree, return the vertical order traversal of its nodes' values.

For the vertical order traversal, list the nodes column by column starting from the leftmost column and moving to the right.

Within each column, the nodes should be listed in the order they appear from the top of the tree to the bottom.

If two nodes are located at the same row and column, the node that appears to the left should come before the other.

Example 1:

Input: root = [3,9,20,null,null,15,7]

Output: [[9],[3,15],[20],[7]]

Example 2:

Input: root = [3,9,8,4,0,1,7]

Output: [[4],[9],[3,0,1],[8],[7]]

Example 3:

Input: root = [1,2,3,4,10,9,11,null,5,null,null,null,null,null,null,null,6]

Output: [[4],[2,5],[1,10,9,6],[3],[11]]

Constraints:

  • 0 <= number of nodes in the tree <= 100
  • -100 <= Node.val <= 100


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Trees - Understanding tree structure and traversal patterns
  • Breadth First Search (BFS) - Level-order traversal ensures correct top-to-bottom ordering within columns
  • Hash Maps - Used to group nodes by their column index for efficient lookup
  • Sorting - Required to order columns from left to right in some approaches

1. Breadth First Search + Sorting

Intuition

We assign each node a column index where the root is at column 0, left children are at column - 1, and right children are at column + 1. Using BFS ensures we visit nodes level by level, so nodes in the same column appear in top-to-bottom order. We group nodes by their column index and sort the columns to get the final result.

Algorithm

  1. Use a hash map to group node values by their column index.
  2. Initialize a queue with the root node and its column index (0).
  3. While the queue is not empty:
    • Dequeue a node and its column index.
    • Add the node's value to the corresponding column list.
    • Enqueue the left child with column - 1 and right child with column + 1.
  4. Sort the column keys and return the values in order.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def verticalOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        cols = defaultdict(list)
        que = deque([(root, 0)])

        while que:
            node, pos = que.popleft()
            if node:
                cols[pos].append(node.val)
                que.append((node.left, pos - 1))
                que.append((node.right, pos + 1))

        return [cols[x] for x in sorted(cols)]

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

2. Depth First Search + Sorting

Intuition

DFS can also solve this problem, but we need to track each node's row to maintain the correct vertical order within columns. Since DFS doesn't naturally visit nodes in level order, we store both the row and value for each node. After traversal, we sort each column by row index to ensure nodes appear in top-to-bottom order.

Algorithm

  1. Use a hash map to store pairs of (row, value) for each column.
  2. Define a dfs function that takes the current node, row, and column.
  3. If the node is null, return.
  4. Add (row, node value) to the column's list.
  5. Recursively call dfs on the left child with (row + 1, column - 1) and right child with (row + 1, column + 1).
  6. Sort the columns by their keys, then sort each column's entries by row.
  7. Extract just the values from each sorted column list.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def verticalOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        cols = defaultdict(list)

        def dfs(node, row, col):
            if not node:
                return
            cols[col].append((row, node.val))
            dfs(node.left, row + 1, col - 1)
            dfs(node.right, row + 1, col + 1)

        dfs(root, 0, 0)

        res = []
        for col in sorted(cols):
            col_vals = sorted(cols[col], key=lambda x: x[0])
            res.append([val for _, val in col_vals])
        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(nlogn)O(n \log n)

3. Breadth First Search (Optimal)

Intuition

Instead of sorting all column keys at the end, we can track the minimum and maximum column indices during traversal. This allows us to iterate from the leftmost to rightmost column directly without sorting, reducing the time complexity.

Algorithm

  1. Use a hash map to group node values by column index.
  2. Track the minimum and maximum column indices during traversal.
  3. Initialize a queue with the root and column 0.
  4. While the queue is not empty:
    • Dequeue a node and its column.
    • Add the value to the column's list and update minCol/maxCol.
    • Enqueue children with their respective column indices.
  5. Iterate from minCol to maxCol and collect each column's values in order.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def verticalOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []

        cols = defaultdict(list)
        queue = deque([(root, 0)])
        minCol = maxCol = 0

        while queue:
            node, col = queue.popleft()
            cols[col].append(node.val)
            minCol = min(minCol, col)
            maxCol = max(maxCol, col)

            if node.left:
                queue.append((node.left, col - 1))
            if node.right:
                queue.append((node.right, col + 1))

        return [cols[c] for c in range(minCol, maxCol + 1)]

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

4. Depth First Search (Optimal)

Intuition

Similar to the optimal BFS approach, we track min and max column indices during DFS to avoid sorting columns. However, we still need to store row information and sort within each column since DFS doesn't visit nodes in level order. This approach reduces the complexity of iterating over columns while still requiring per-column sorting.

Algorithm

  1. Use a hash map to store (row, value) pairs for each column.
  2. Track minCol and maxCol column indices during traversal.
  3. Define a dfs function that updates minCol/maxCol and adds (row, value) to the column list.
  4. Recursively traverse left and right children with updated row and column values.
  5. After dfs completes, iterate from minCol to maxCol.
  6. For each column, sort entries by row and extract the values.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def verticalOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []

        cols = defaultdict(list)
        minCol = maxCol = 0

        def dfs(node, row, col):
            nonlocal minCol, maxCol
            if not node:
                return
            cols[col].append((row, node.val))
            minCol = min(minCol, col)
            maxCol = max(maxCol, col)
            dfs(node.left, row + 1, col - 1)
            dfs(node.right, row + 1, col + 1)

        dfs(root, 0, 0)

        res = []
        for c in range(minCol, maxCol + 1):
            # sort by row only
            col_vals = sorted(cols[c], key=lambda x: x[0])
            res.append([val for _, val in col_vals])
        return res

Time & Space Complexity

  • Time complexity: O(whlogh)O(w * h \log h)
  • Space complexity: O(n)O(n)

Where nn is the number of nodes, hh is the height of the tree (i.e. maximum number of nodes in any vertical line of the tree), and ww is the width of the tree (i.e. maximum number of nodes in any of the levels of the tree).


Common Pitfalls

Using DFS Without Tracking Row Index

DFS does not visit nodes level by level, so nodes in the same column may be visited out of order. Without storing the row index alongside each value, nodes at the same column will appear in traversal order rather than top-to-bottom order, violating the problem's requirement.

Forgetting to Handle the Left-Before-Right Ordering

When two nodes share the same row and column (which can happen with certain tree structures), the node that appears first from left to right in the tree should come first in the result. BFS naturally handles this, but DFS with unstable sorting can break this ordering.

# Wrong: using regular sort instead of stable sort
cols[col].sort(key=lambda x: x[0])  # May reorder same-row nodes

Sorting Columns Instead of Tracking Min/Max

Sorting all column keys at the end works but is less efficient. A common mistake is not realizing that column indices are consecutive integers from minCol to maxCol, allowing direct iteration without sorting.