Vertical Order Traversal of a Binary Tree

104k
0

Given the root of a binary tree, return its vertical order traversal from left to right.

A coordinate is assigned to every node:

  • The root is placed at row = 0, column = 0.

  • The left child of a node at (row, column) is placed at (row + 1, column - 1).

  • The right child is placed at (row + 1, column + 1).

Nodes are grouped according to their column.

Within each column:

  • Nodes with a smaller row appear first.

  • If multiple nodes have the same row and column, their values appear in ascending order.

The traversal is returned as a list of columns from the smallest column to the largest.

Example 1

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

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

Explanation:
Node 9 lies at column -1. Nodes 3 and 15 lie at column 0, with 3 appearing first because it has the smaller row. Node 20 lies at column 1, and node 7 lies at column 2.

Example 2

Input:
root = [1, 2, 3, 4, 6, 5, 7]

Output:
[[4], [2], [1, 5, 6], [3], [7]]

Explanation:
Nodes 5 and 6 occupy the same row and column. Their values are therefore placed in ascending order as 5, 6. The columns are returned from left to right.

Approach 1

Vertical traversal depends on three pieces of information:

column → row → value

The column determines the vertical line to which a node belongs. The row determines its top-to-bottom position within that column, while the node value resolves ties when multiple nodes occupy the same row and column.

Therefore, every node can be stored as:

(column, row, value)

Once all nodes have been collected, these entries can be sorted by column, then row, and finally value. This directly produces the ordering required by the problem.

While the sorted entries are being converted into the final result, a variable previousColumn is used to remember the column of the previously processed entry. Whenever the current column differs from previousColumn, a new vertical list is started. This allows consecutive nodes belonging to the same column to be grouped together efficiently.

Algorithm

  • The tree is traversed while the row and column of every node are tracked, and each node is stored as (column, row, value) so that all required ordering information is preserved.

  • For every left child, the coordinates are transformed to (row + 1, column - 1), while every right child is assigned (row + 1, column + 1).

  • After traversal, all stored entries are sorted first by column, then by row, and finally by node value so that the required vertical ordering is obtained.

  • A variable previousColumn is maintained while the sorted entries are processed. Whenever a different column is encountered, a new vertical list is created.

  • Nodes having the same column are appended to the current vertical list, and all completed lists are stored from the smallest column to the largest.

  • The grouped vertical lists are returned as the final traversal.Dry Run

Vertical Traversal Appraoch 1 Dry Run.png

Vertical Traversal Appraoch 1 Dry Run.png

Solution

#include <bits/stdc++.h>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int value) {
val = value;
left = nullptr;
right = nullptr;
}
};
class Solution {
private:
// Stores every node with its
// column, row, and value.
void dfs(
TreeNode* node,
int row,
int column,
vector<tuple<int, int, int>>& nodes
) {
if (node == nullptr) {
return;
}
nodes.push_back({
column,
row,
node->val
});
// Moving left increases the row
// and decreases the column.
dfs(
node->left,
row + 1,
column - 1,
nodes
);
// Moving right increases both
// the row and column.
dfs(
node->right,
row + 1,
column + 1,
nodes
);
}
public:
// Returns nodes grouped by vertical
// columns from left to right.
vector<vector<int>> verticalTraversal(
TreeNode* root
) {
if (root == nullptr) {
return {};
}
vector<tuple<int, int, int>> nodes;
dfs(root, 0, 0, nodes);
// Tuple sorting automatically follows
// column, then row, then value.
sort(nodes.begin(), nodes.end());
vector<vector<int>> answer;
int previousColumn = INT_MIN;
// A new group is started whenever
// the column changes.
for (auto& entry : nodes) {
auto [column, row, value] = entry;
if (column != previousColumn) {
answer.push_back({});
previousColumn = column;
}
answer.back().push_back(value);
}
return answer;
}
};
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(9);
root->right = new TreeNode(20);
root->right->left = new TreeNode(15);
root->right->right = new TreeNode(7);
Solution solution;
vector<vector<int>> answer =
solution.verticalTraversal(root);
for (auto& column : answer) {
for (int value : column) {
cout << value << " ";
}
cout << endl;
}
return 0;
}

Complexity Analysis

Time Complexity: O(N log N), where N is the number of nodes in the binary tree. All N nodes are stored and then sorted according to their column, row, and value.

Space Complexity: O(N), where N is the number of nodes in the binary tree. Coordinate information for all nodes may need to be stored before the final vertical groups are constructed.

Approach 2

Instead of collecting every node first and sorting everything afterward, the nodes can be organized according to their coordinates during traversal itself.

A nested ordered structure is used:

map<int, map<int, multiset<int>>>

Each level of this structure has a specific purpose:

  • The outer map uses the column as its key, so vertical lines remain ordered from left to right.

  • The inner map uses the row as its key, so nodes within each column remain ordered from top to bottom.

  • The multiset stores values of nodes that share the exact same (column, row) position and automatically keeps those values in ascending order.

This structure directly matches the ordering rules of the problem and avoids requiring one global sort over all node coordinates.

BFS can then be used to traverse the tree while carrying each node's row and column.

Algorithm

  • If the root is non-null, a queue is initialized with the root at (row = 0, column = 0) so that coordinate information can be carried during BFS.

  • A nested ordered structure of the form column → row → sorted values is maintained, allowing columns, rows, and equal-position values to remain ordered automatically.

  • Whenever a node is removed from the queue, its value is inserted into the collection corresponding to its current (column, row) position.

  • For every left child, (row + 1, column - 1) is assigned, while every right child is assigned (row + 1, column + 1), preserving their correct vertical positions.

  • After traversal, the outer map is processed from the smallest column to the largest, while each inner map is processed from the smallest row to the largest. Values stored in each multiset are appended in their already sorted order.

  • Each completed column is added to the final result, producing the required vertical traversal from left to right.

Dry Run

Vertical Traversal Appraoch 2 Dry Run.png

Vertical Traversal Appraoch 2 Dry Run.png

Solution

#include <bits/stdc++.h>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int value) {
val = value;
left = nullptr;
right = nullptr;
}
};
class Solution {
public:
// Organizes nodes directly by
// column, row, and sorted value.
vector<vector<int>> verticalTraversal(
TreeNode* root
) {
if (root == nullptr) {
return {};
}
// Outer map orders columns, inner map
// orders rows, multiset sorts equal positions.
map<int, map<int, multiset<int>>> nodes;
queue<pair<TreeNode*, pair<int, int>>> q;
q.push({
root,
{0, 0}
});
// BFS carries the coordinate
// of every visited node.
while (!q.empty()) {
auto current = q.front();
q.pop();
TreeNode* node = current.first;
int column = current.second.first;
int row = current.second.second;
nodes[column][row].insert(
node->val
);
// A left child moves one row down
// and one column to the left.
if (node->left != nullptr) {
q.push({
node->left,
{column - 1, row + 1}
});
}
// A right child moves one row down
// and one column to the right.
if (node->right != nullptr) {
q.push({
node->right,
{column + 1, row + 1}
});
}
}
vector<vector<int>> answer;
// Nested iteration preserves column,
// row, and ascending-value order.
for (auto& columnEntry : nodes) {
vector<int> columnValues;
for (auto& rowEntry : columnEntry.second) {
for (int value : rowEntry.second) {
columnValues.push_back(value);
}
}
answer.push_back(columnValues);
}
return answer;
}
};
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(9);
root->right = new TreeNode(20);
root->right->left = new TreeNode(15);
root->right->right = new TreeNode(7);
Solution solution;
vector<vector<int>> answer =
solution.verticalTraversal(root);
for (auto& column : answer) {
for (int value : column) {
cout << value << " ";
}
cout << endl;
}
return 0;
}

Complexity Analysis

Time Complexity: O(N log N) in the worst case, where N is the number of nodes in the binary tree. Every node is inserted into ordered maps and multisets, and these operations can require logarithmic time.

Space Complexity: O(N), where N is the number of nodes in the binary tree. The nested map structure and BFS queue may together store information for up to all nodes.

Interview follow-up Questions

The column determines which vertical line a node belongs to, while the row determines its top-to-bottom position within that vertical line.

Binary Tree

Read Similar Blogs

Comments0