Binary Tree Zigzag Level Order Traversal

Medium BFS

Problem

Given the root of a binary tree, return the zigzag level order traversal of its nodes' values (i.e., from left to right, then right to left for the next level and alternate between).

  • The number of nodes in the tree is in the range [0, 2000]
  • −100 ≤ Node.val ≤ 100

Example

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

The traversal starts at the root level with value 3, traversed left to right. The next level contains nodes 9 and 20, traversed right to left, so the order is [20, 9]. The final level contains nodes 15 and 7, traversed left to right again, resulting in [15, 7]. The alternating direction at each level creates the zigzag pattern.

Approach

Straightforward Solution

A naive approach performs a standard BFS level order traversal and reverses the list of node values for every alternate level after the traversal. This requires extra passes or additional space to reverse the lists.

Core Observation

A level order traversal naturally processes nodes level by level. The zigzag pattern requires reversing the order of node values at every other level, which can be achieved by controlling the insertion order of node values during traversal.

Path to Optimal

Instead of reversing after traversal, the algorithm uses a double-ended queue (deque) to insert node values either at the end or the front depending on the current traversal direction. This approach integrates the zigzag ordering into the traversal itself, avoiding extra reversal steps and maintaining O(n) time complexity.

Optimal Approach

Use a BFS queue to traverse the tree level by level. For each level, use a deque to collect node values. If traversing left to right, append values to the right; if right to left, append values to the left. After processing each level, toggle the direction flag. This approach yields the zigzag order in a single pass with O(n) time and O(n) space.

Time

O(n)

Each node is enqueued and dequeued exactly once during BFS, and each node's value is inserted into the deque once, resulting in linear time proportional to the number of nodes.

Space

O(w) (worst-case O(n))

The BFS queue and the per-level deque each store at most the number of nodes on a single level (the tree's maximum width w). Therefore the auxiliary space is O(w), which is O(n) in the worst case.

Pattern Spotlight

BFS (Level Order Traversal with Direction Toggle)

When a problem requires level order traversal with alternating processing order, integrate direction control into the traversal by using a deque and toggling insertion ends, avoiding costly post-processing reversals.

Solution

1from collections import deque
2
3class Solution:
4 def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
5 if not root:
6 return []
7
8 res = []
9 q = deque([root])
10 left_to_right = True
11
12 while q:
13 level_size = len(q)
14 level = deque()
15
16 for _ in range(level_size):
17 node = q.popleft()
18
19 if left_to_right:
20 level.append(node.val)
21 else:
22 level.appendleft(node.val)
23
24 if node.left:
25 q.append(node.left)
26 if node.right:
27 q.append(node.right)
28
29 res.append(list(level))
30 left_to_right = not left_to_right
31
32 return res

Step-by-Step Solution

1

Handle Empty Tree Edge Case

5if not root:
6 return []

Objective

To immediately return an empty list if the input tree is empty, as there are no nodes to traverse.

Key Insight

Checking for an empty root node upfront prevents unnecessary setup and traversal logic. This early exit handles the trivial case efficiently and avoids null pointer errors during BFS.

Interview Quick-Check

State & Boundaries

Return [] immediately if root is None, as no traversal is possible.

2

Initialize BFS Queue and Direction Flag

8res = []
9q = deque([root])
10left_to_right = True

Objective

To set up the initial data structures for BFS traversal and track the current traversal direction.

Key Insight

The queue holds nodes to be processed level by level, starting with the root. The boolean flag `left_to_right` controls whether node values are appended to the right or left of the deque for the current level, enabling zigzag ordering.

Interview Quick-Check

Core Logic

Initialize the queue with the root node and set the direction flag to True to start traversal from left to right.

Common Pitfalls & Bugs

Forgetting to initialize the direction flag or queue correctly can cause incorrect traversal order or runtime errors.

3

Traverse Tree Level by Level with Zigzag Ordering

12while q:
13 level_size = len(q)
14 level = deque()
16 for _ in range(level_size):
17 node = q.popleft()
19 if left_to_right:
20 level.append(node.val)
21 else:
22 level.appendleft(node.val)
24 if node.left:
25 q.append(node.left)
26 if node.right:
27 q.append(node.right)
29 res.append(list(level))
30 left_to_right = not left_to_right

Objective

To perform BFS traversal, collecting node values in zigzag order by conditionally appending to the deque based on the current direction.

Key Insight

At each level, the BFS queue still dequeues nodes in left-to-right structural order. Zigzag affects only the order in which we record values. Collect values into a deque level: if left_to_right is True, append to the right; otherwise, appendleft to the left. appendleft places each newly visited value at the front, so the level is constructed in reverse order during traversal, eliminating any reversal step afterward. After processing exactly level_size nodes (the current level), append list(level) to res (output format) and toggle left_to_right (flip direction for the next level).

Interview Quick-Check

Core Logic

Process each level by dequeuing nodes, appending their values to the level deque in the correct order, enqueueing their children, and toggling the direction flag for the next level.

State & Boundaries

Use a for loop over the current queue size to ensure all nodes at the current level are processed before toggling direction.

Common Pitfalls & Bugs

Appending node values to the wrong end of the deque or toggling the direction flag incorrectly can break the zigzag pattern.

Complexity

This approach visits each node once for O(n) time and uses O(w) auxiliary space (worst-case O(n)), where w is the maximum number of nodes on any level.

4

Return the Zigzag Level Order Result

32return res

Objective

To return the final list of lists containing node values in zigzag level order after traversal completes.

Key Insight

After BFS completes and all levels have been processed with alternating directions, the accumulated result list contains the desired zigzag order. Returning this list completes the solution.

Interview Quick-Check

Core Logic

Return the accumulated result list after processing all levels.

Line Analysis

This solution has 1 Critical line interviewers watch for.

Line 22 Critical
level.appendleft(node.val)

Insert the node value at the front of the level deque for right-to-left ordering.

This is the core zigzag operation: inserting at the front constructs the reversed order during traversal, avoiding any per-level reversal step afterward.

Line 5 Core
if not root:

Check if the root node is None.

This early check prevents unnecessary processing and handles the edge case of an empty tree, ensuring the function returns immediately with an empty list.

Line 6 Core
return []

Return an empty list if the tree is empty.

Returning [] here correctly signals that there are no nodes to traverse, satisfying the problem's requirement for empty input.

Line 14 Core
level = deque()

Initialize a deque to collect node values for the current level.

Using a deque allows efficient insertion at both ends, which is essential for implementing zigzag ordering without extra reversal steps.

Line 20 Core
level.append(node.val)

Append the node's value to the right end of the deque for left-to-right traversal.

Appending to the right preserves the natural order of nodes when traversing left to right.

Line 29 Core
res.append(list(level))

Append the current level's node values as a list to the result.

Converting the deque to a list and appending it preserves the zigzag order for this level in the final output.

Line 30 Core
left_to_right = not left_to_right

Toggle the traversal direction for the next level.

Flipping the direction flag ensures the zigzag pattern alternates between left-to-right and right-to-left at each level.

Line 9 Core
q = deque([root])

Initialize the BFS queue with the root node.

The queue manages nodes to be processed level by level, starting with the root, enabling breadth-first traversal.

Line 10 Core
left_to_right = True

Set the initial traversal direction to left to right.

The boolean flag controls the insertion order of node values at each level, enabling the zigzag pattern by toggling after each level.

Line 13 Core
level_size = len(q)

Determine the number of nodes at the current level.

Capturing the current queue size allows processing exactly one level per iteration, maintaining level boundaries.

Line 17 Core
node = q.popleft()

Dequeue the next node to process.

Removing nodes from the front of the queue maintains BFS order, processing nodes level by level.

Line 19 Core
if left_to_right:

Check if the current traversal direction is left to right.

This condition determines how to insert the current node's value into the level deque to maintain zigzag order.

Line 24 Core
if node.left:

Check whether the current node has a left child to enqueue.

This condition prevents enqueuing null children and ensures the next level is fully populated for BFS traversal.

Line 25 Core
q.append(node.left)

Enqueue the left child into the BFS queue.

Enqueuing the left child schedules it for processing in the next BFS layer, preserving correct level boundaries and full coverage.

Line 26 Core
if node.right:

Check whether the current node has a right child to enqueue.

This mirrors the left-child handling to ensure the right subtree is also traversed; omitting it would skip nodes.

Line 27 Core
q.append(node.right)

Enqueue the right child into the BFS queue.

Enqueuing the right child completes adding this node’s children for the next level, maintaining BFS completeness and order.

Line 32 Core
return res

Return the final zigzag level order traversal result.

Returning the accumulated list completes the function, providing the correct zigzag traversal as required.

Line 12 Standard
while q:

Start the BFS loop, continuing while there are nodes to process.

The while loop ensures all levels are processed until the queue is empty, covering the entire tree.

Line 16 Standard
for _ in range(level_size):

Iterate over all nodes at the current level.

This loop processes each node in the current level, ensuring all nodes are handled before moving to the next level.

Line 21 Standard
else:

Enter the right-to-left insertion branch for this level.

This branch activates the right-to-left behavior for the current level. The actual zigzag effect is applied by the appendleft operation inside this branch.

Line 8 Basic
res = []

Initialize the result list to store the zigzag level order traversal.

This list accumulates the final output, collecting each level's node values in the correct zigzag order.

Test Your Understanding

Why does using a deque with conditional append or appendleft operations eliminate the need to reverse the level list after traversal?

Because inserting node values at the front or back of the deque during traversal directly constructs the zigzag order, the final list for each level is already in the correct order, making post-traversal reversal unnecessary.

Related Problems

BFS pattern

Don't just read it. Drill it.

Reconstruct Binary Tree Zigzag Level Order Traversal from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Binary Tree Zigzag Level Order Traversal drill

or drill a free problem