Topics › Binary Trees › Medium Problems
Zig Zag or Spiral Traversal
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given the root of a binary tree, return its zigzag level order traversal: the values level by level from top to bottom, where the first level (the root) is read left to right, the second right to left, the third left to right again, and so on. Return a list with one inner list per level. An empty tree gives an empty list.
Constraints: 0 ≤ n ≤ 2000 nodes in the LeetCode version, values fit in an int.
Example 1
tree (level order) = [3, 9, 20, null, null, 15, 7]
3
/ \
9 20
/ \
15 7
answer = [[3], [20, 9], [15, 7]] // level 1 is read right to left
Example 2
tree (level order) = [1, 2, 3, 4, 5, 6, 7]
1
/ \
2 3
/ \ / \
4 5 6 7
answer = [[1], [3, 2], [4, 5, 6, 7]]
This is ordinary level order traversal with one twist: every second level comes out reversed. The interesting part is choosing where to do the reversing. The node visiting order can stay a normal breadth-first order; only the order in which values are written down needs to alternate.
Approach 1: Level order, then reverse every second level
Intuition. Do a standard BFS, collecting one list per level. Keep a boolean leftToRight that flips after every level, and when it is false reverse the list before storing it. This is the simplest correct solution and the natural first answer.
Algorithm.
- If the root is
null, return an empty list. Otherwise enqueue the root and setleftToRight = true. - While the queue is not empty:
- Let
sizebe the queue size and pollsizenodes intorow, enqueuing their existing children left then right. - If
leftToRightisfalse, reverserow. - Append
rowand flipleftToRight.
- Let
- Return the list of rows.
static List<List<Integer>> reverseAlternateLevels(TreeNode root) {
List<List<Integer>> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> q = new ArrayDeque<>();
q.add(root);
boolean leftToRight = true;
while (!q.isEmpty()) {
int size = q.size();
List<Integer> row = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
row.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
if (!leftToRight) Collections.reverse(row);
out.add(row);
leftToRight = !leftToRight;
}
return out;
}
Complexity. Time O(n): each node is enqueued and polled once, and reversing a level costs its length, so all reversals together cost O(n). Space O(w) beyond the output, where w is the widest level.
Approach 2: Two stacks
Intuition. A stack reverses the order of whatever is pushed onto it, and zigzag is “reverse the direction at every level”. So hold the current level in one stack and push the children into a second stack. Popping a stack always produces the reverse of the push order, so if we are careful about which child we push first, the next level automatically comes out in the opposite direction:
- When the current level is being read left to right, push each node’s left child, then its right child. The next stack then pops right to left.
- When reading right to left, push right then left. The next stack pops left to right.
Algorithm.
- Push the root onto
cur;leftToRight = true. - While
curis not empty: create an emptynextstack androw; pop every node fromcur, append it torow, and push its children ontonextin the order given above. Appendrow, setcur = next, flipleftToRight.
static List<List<Integer>> twoStacks(TreeNode root) {
List<List<Integer>> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> cur = new ArrayDeque<>();
cur.push(root);
boolean leftToRight = true;
while (!cur.isEmpty()) {
Deque<TreeNode> next = new ArrayDeque<>();
List<Integer> row = new ArrayList<>();
while (!cur.isEmpty()) {
TreeNode node = cur.pop();
row.add(node.val);
if (leftToRight) {
if (node.left != null) next.push(node.left);
if (node.right != null) next.push(node.right);
} else {
if (node.right != null) next.push(node.right);
if (node.left != null) next.push(node.left);
}
}
out.add(row);
cur = next;
leftToRight = !leftToRight;
}
return out;
}
Dry run on Example 2:
| level | read direction | pop order | pushed onto next (bottom to top) | row |
|---|---|---|---|---|
| 0 | left to right | 1 | 2, 3 | [1] |
| 1 | right to left | 3, 2 | 7, 6, 5, 4 | [3, 2] |
| 2 | left to right | 4, 5, 6, 7 | nothing | [4, 5, 6, 7] |
Complexity. Time O(n): each node is pushed and popped once. Space O(w): the two stacks together hold at most two adjacent levels. The approach is elegant and is a classic interview alternative, but it is easy to get the push order wrong, so verify it on a three-level tree as in the table.
Approach 3: Optimal, write each value into its final slot
Intuition. In Approach 1 we write the values of a reversed level in the wrong order and then fix them. But at the start of each level we already know its size, so we know where each value belongs: the i-th node polled from the queue (always in left-to-right order) goes to index i on a left-to-right level, and to index size - 1 - i on a right-to-left level. Allocate the row with exactly size slots and write each value straight into place. The BFS stays completely standard, and there is no second pass over any level.
Algorithm.
- Standard BFS with
leftToRight = true. - For each level of
sizenodes, create an arrayrowof lengthsize. - For the
i-th polled node, store its value atiifleftToRight, else atsize - 1 - i; enqueue its children left then right. - Append the row, flip
leftToRight.
static List<List<Integer>> optimal(TreeNode root) {
List<List<Integer>> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> q = new ArrayDeque<>();
q.add(root);
boolean leftToRight = true;
while (!q.isEmpty()) {
int size = q.size();
Integer[] row = new Integer[size];
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
row[leftToRight ? i : size - 1 - i] = node.val;
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
out.add(new ArrayList<>(Arrays.asList(row)));
leftToRight = !leftToRight;
}
return out;
}
The row is built as an Integer[] so that it can be wrapped with Arrays.asList (which would not work on an int[]), then copied into an ArrayList so that the caller receives an ordinary modifiable list.
Complexity. Time O(n): one constant-time write per node. Space O(w) for the queue. All three approaches have the same asymptotic cost; this one does the least work per node and keeps the traversal itself untouched, which makes it the easiest to reason about.
Edge cases and pitfalls
- Empty tree. Return an empty list, not a list containing an empty level.
- Single node.
[[1]]: the first level is read left to right. - Missing children. Gaps in a level do not matter. In
[1, 2, 3, 4, null, null, 5]the last level is[4, 5], read left to right because it is the third level. - Only the reading direction changes. Do not enqueue children right-to-left on reversed levels in the queue-based approaches: that changes the visiting order of the next level too, and the directions stop alternating correctly. The two-stack approach is the one place where push order is deliberately changed, because a stack reverses it again.
- Snapshot the level size. Read
q.size()once before the inner loop; it changes while children are added. - Testing. Besides the hand-written cases, including a four-level perfect tree, the Java file compares all three methods on 300 seeded random trees.
Related problems
- Level Order Traversal is the plain BFS this problem modifies.
- Right/Left View of Binary Tree also processes one level at a time and keeps only one end of each level.
- Maximum Width of BT uses positions inside a level, similar to the index trick of Approach 3.
- Vertical Order Traversal orders nodes by column instead of by level.