Quick Overview

Return the vertical traversal of a binary tree using column, row, and value ordering with explicit tie rules. The exercise evaluates coordinate propagation, iterative handling of maximum-height trees, duplicate values, empty input, deterministic grouping, portable array representation, and sorting complexity.

Return the Vertical Traversal of a Binary Tree

Company: Amazon

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: hard

Interview Round: Onsite

## Return the Vertical Traversal of a Binary Tree ### Problem Implement `verticalTraversal(nodeValues, left, right, root) -> columns`. Node `root` is at row `0`, column `0`. A left child is at `(row + 1, column - 1)`, and a right child is at `(row + 1, column + 1)`. Return the node values grouped from the smallest column to the largest column. Within one column, order nodes by increasing row. When two nodes share both row and column, order their values in increasing numeric order. ### Function Contract - Python: `def verticalTraversal(nodeValues: list[int], left: list[int], right: list[int], root: int) -> list[list[int]]` - JavaScript: `function verticalTraversal(nodeValues, left, right, root)` returns a nested integer array. - Java: `List<List<Integer>> verticalTraversal(List<Integer> nodeValues, List<Integer> left, List<Integer> right, int root)` - C++: `vector<vector<int>> verticalTraversal(const vector<int>& nodeValues, const vector<int>& left, const vector<int>& right, int root)` ### Portable Contract - The three arrays have the same length `n`, with `0 <= n <= 6,000`. - Node IDs are `0` through `n - 1`. `left[i]` and `right[i]` are child IDs or `-1` when absent. - When `n == 0`, `root == -1` and the result is empty. Otherwise `0 <= root < n`. - For nonempty input, the child arrays describe one valid binary tree: every node is reachable from `root` exactly once, with no cycles or shared child. - `-1,000,000,000 <= nodeValues[i] <= 1,000,000,000`; duplicate values are allowed. - Do not modify any input array. - Let `B` be the compact UTF-8 JSON byte length of `[nodeValues,left,right,root]`, with no whitespace and every bracket, comma, minus sign, and digit counted. Inputs satisfy `B <= 96,000`. - Let `R` be the compact UTF-8 JSON byte length of the returned nested integer array under the same rule. Inputs guarantee `R <= 96,000`, so input plus output is at most `192,000` bytes. - Target `O(n log n)` time and `O(n)` auxiliary space. Traversal must handle a height-`n` tree without depending on a shallow recursion limit. ### Examples ```text nodeValues = [3, 9, 20, 15, 7] left = [1, -1, 3, -1, -1] right = [2, -1, 4, -1, -1] root = 0 columns = [[9], [3, 15], [20], [7]] ``` ```text nodeValues = [1, 2, 3, 4, 5, 6, 7] left = [1, 3, 5, -1, -1, -1, -1] right = [2, 4, 6, -1, -1, -1, -1] root = 0 columns = [[4], [2], [1, 5, 6], [3], [7]] ``` Nodes `5` and `6` share row `2`, column `0`, so their values are sorted. ```text nodeValues = [] left = [] right = [] root = -1 columns = [] ``` ```hint Record coordinates during traversal Once each node has a column and row, the output rule becomes an ordering problem over those records. ``` ```hint Include every tie-breaker Column determines the group, row determines vertical order, and value resolves nodes at the same coordinate. ``` ### Discussion Requirements 1. Explain how coordinates propagate from parent to child. 2. State the complete sort key and how columns are grouped afterward. 3. Cover duplicate values, several nodes at one coordinate, an empty tree, one node, and a maximum-height tree. 4. Compare collecting and sorting all records with maintaining an ordered map of columns and rows.

Overview: Return the vertical traversal of a binary tree using column, row, and value ordering with explicit tie rules. The exercise evaluates coordinate propagation, iterative handling of maximum-height trees, duplicate values, empty input, deterministic grouping, portable array representation, and sorting complexity.

Read the full Amazon Software Engineer interview experience this question came from

Given a binary tree that is supplied as three parallel arrays plus a root id, return its node values grouped into vertical columns. The tree has `n` nodes with ids `0` through `n - 1`. `nodeValues[i]` is the value stored at node `i`, `left[i]` is the id of node `i`'s left child, and `right[i]` is the id of its right child; a child id of `-1` means that child is absent. `root` is the id of the root node. Place the root at row `0`, column `0`. A left child of a node at `(row, column)` sits at `(row + 1, column - 1)`, and a right child sits at `(row + 1, column + 1)`. Return a list of columns, ordered from the smallest column number to the largest. Only columns that actually contain at least one node appear in the result. Within a single column, list node values by increasing row. When two nodes share the same row **and** the same column, list their values in increasing numeric order. Note that node ids carry no ordering information: `root` is not necessarily `0`, and a node's id says nothing about its row or column. Also note that `-1` is a legal node **value** as well as the "no child" sentinel for `left` and `right`; the two uses are unrelated. Do not modify any of the input arrays. ### Output semantics The result is fully determined. Sort every node by the triple `(column, row, value)` in ascending order, then emit one inner list per distinct column, in ascending column order, containing that column's values in that same sorted order. All three levels of the key are required. ### Examples Example 1: ```text nodeValues = [3, 9, 20, 15, 7] left = [1, -1, 3, -1, -1] right = [2, -1, 4, -1, -1] root = 0 returns [[9], [3, 15], [20], [7]] ``` Node `0` (value `3`) is at `(0, 0)`. Node `1` (value `9`) is at `(1, -1)`, node `2` (value `20`) at `(1, 1)`, node `3` (value `15`) at `(2, 0)`, and node `4` (value `7`) at `(2, 2)`. The occupied columns are `-1, 0, 1, 2`, so the smallest column `-1` (holding `9`) is emitted first, then column `0` where `3` precedes `15` because row `0` precedes row `2`. Example 2: ```text nodeValues = [1, 2, 3, 4, 5, 6, 7] left = [1, 3, 5, -1, -1, -1, -1] right = [2, 4, 6, -1, -1, -1, -1] root = 0 returns [[4], [2], [1, 5, 6], [3], [7]] ``` Values `5` and `6` both land at row `2`, column `0`, so the value tie-break puts `5` before `6`. Value `1` precedes both because it sits at row `0` of the same column. Example 3 (empty tree): ```text nodeValues = [] left = [] right = [] root = -1 returns [] ```

Constraints

  • nodeValues, left, and right all have the same length n, with 0 <= n <= 6000
  • Node ids are 0 through n - 1; left[i] and right[i] are each either a child id in 0..n-1 or -1 when that child is absent
  • When n == 0, root == -1 and the result is the empty list; otherwise 0 <= root < n (root is not necessarily 0)
  • For nonempty input the child arrays always describe one valid binary tree: every node is reachable from root exactly once, with no cycles and no shared child
  • -1000000000 <= nodeValues[i] <= 1000000000; duplicate values are allowed, and a nodeValue may itself be -1 (unrelated to the -1 child sentinel)
  • Do not modify any input array
  • Let B be the compact UTF-8 JSON byte length of [nodeValues, left, right, root], with no whitespace and every bracket, comma, minus sign, and digit counted; inputs satisfy B <= 96000
  • Let R be the compact UTF-8 JSON byte length of the returned nested integer array under the same rule; inputs guarantee R <= 96000, so input plus output is at most 192000 bytes
  • Target O(n log n) time and O(n) auxiliary space; traversal must handle a height-n tree without depending on a shallow recursion limit

Examples

Input: ([],[],[],-1)

Expected Output: []

Input: ([42],[-1],[-1],0)

Expected Output: [[42]]

Hints

  1. A node's row and column depend only on the path from the root, so you can compute every coordinate in one pass before thinking about the output at all.
  2. Column, then row, then value is a single combined ordering key rather than three separate passes. Once records are in that order, the column groups are already contiguous.
  3. n reaches 6000 and the tree may be one long chain, so carry each node's coordinate on an explicit stack or queue rather than relying on the language's call stack. Watch out for how your language orders negative keys if you group by column in a map.

Loading coding console...

Show the approach

Approach

Every node's coordinate is decided entirely by the path taken from the root, so the whole problem separates cleanly into two phases: assign coordinates, then order the results.

Phase 1 - assign coordinates. Walk the tree once, carrying (nodeId, row, column) along the way. Starting from (root, 0, 0), a left child inherits (row + 1, column - 1) and a right child (row + 1, column + 1). Each visit emits one record (column, row, value). Because the input is guaranteed to be a single valid binary tree, every node is visited exactly once and the record list has exactly n entries.

The traversal must be iterative. n can reach 6000 and the tree is allowed to be a single chain, so a height-6000 recursion overflows CPython's default frame limit and threatens the native stack in C++ and Java. Pushing frames onto an explicit stack (or queue - the order in which nodes are visited does not matter, since the sort fixes the output order) removes that dependency entirely. Every node is pushed exactly once, so the stack is bounded by n.

Phase 2 - order and group. The required output order is precisely the ascending lexicographic order of the triple (column, row, value), so a single sort of the record list resolves the column grouping and both tie-break levels simultaneously. After sorting, all records sharing a column are contiguous, so the groups are produced by scanning once and starting a new inner list wherever the column value changes. Sorting a copy of the records rather than the inputs also satisfies the "do not modify any input array" requirement for free.

Two traps worth naming. First, do not group records into a plain JavaScript object keyed by column. JavaScript enumerates integer-index keys first, in numeric order, and only then the remaining string keys in insertion order - so "-1" is pushed behind "0", "1", "2", and Example 1 would come back as [[3, 15], [20], [7], [9]]. A Map with an explicit numeric key sort, an ordered map such as C++'s std::map, or sorting a flat record array all avoid this; a hash map such as std::unordered_map or a string sort of the keys ("-10" < "-2") does not. Second, all three components of the sort key are mandatory: dropping the value tie-break mis-orders Example 2, and dropping the row comparison mis-orders any column that is reached at several different depths.

Sorting n records dominates the running time at O(n log n); the record list, the traversal stack, and the output all use O(n) space.

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