103. Binary Tree Zigzag Level Order Traversal
Problem Description
You are given the root of a binary tree. Your task is to traverse the tree level by level in a zigzag pattern and return the node values.
The zigzag pattern means:
- First level: traverse from left to right
- Second level: traverse from right to left
- Third level: traverse from left to right
- Continue alternating the direction for each subsequent level
For example, if you have a binary tree like:
3 / \ 9 20 / \ 15 7
The zigzag level order traversal would be: [[3], [20, 9], [15, 7]]
- Level 1:
[3]
(left to right) - Level 2:
[20, 9]
(right to left) - Level 3:
[15, 7]
(left to right)
The solution uses a breadth-first search (BFS) approach with a queue to process nodes level by level. A boolean flag left
tracks whether the current level should be processed left-to-right (left = 1
) or right-to-left (left = 0
). When left
is false, the level's values are reversed using t[::-1]
before adding to the result. The flag is toggled after each level using XOR operation left ^= 1
.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: A binary tree is a special type of graph with nodes (tree nodes) and edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure.
DFS?
- Not necessarily: While DFS could work, the problem specifically asks for level-by-level traversal. We need to process all nodes at one level before moving to the next level, which is a characteristic of BFS rather than DFS.
Let's reconsider the path:
Is it a graph?
- Yes: A binary tree is a graph.
Is it a tree?
- No (for this specific traversal pattern): While it IS a tree structurally, the level-order traversal requirement means we should explore other paths in the flowchart.
Is the problem related to directed acyclic graphs?
- No: This is specifically about tree traversal, not general DAG problems.
Is the problem related to shortest paths?
- No: We're not finding shortest paths; we're traversing all nodes level by level.
Does the problem involve connectivity?
- No: We're not checking if nodes are connected; we're traversing an already connected tree.
Does the problem have small constraints?
- No: The tree could be large, and we need an efficient solution.
BFS?
- Yes: BFS is the natural choice for level-order traversal as it processes nodes level by level using a queue.
Conclusion: The flowchart leads us to BFS (Breadth-First Search) for this zigzag level order traversal problem. BFS naturally processes nodes level by level, which is exactly what we need. We then add the zigzag logic by alternating the direction of traversal for each level.
Intuition
When we need to traverse a binary tree level by level, BFS immediately comes to mind because it naturally processes nodes in layers - visiting all nodes at depth 0, then depth 1, then depth 2, and so on. This is exactly what a queue-based approach gives us.
The key insight for the zigzag pattern is that we don't need to change how we traverse the tree - we still visit nodes level by level from left to right. What changes is how we store the results. Think of it like reading a book where you read every line from left to right, but when writing your notes, you alternate between writing left-to-right and right-to-left.
Here's the thought process:
-
Level-order traversal foundation: Use a queue to process nodes level by level. Add the root, then repeatedly process all nodes at the current level while adding their children for the next level.
-
Tracking level boundaries: We need to know when one level ends and the next begins. By processing exactly
len(q)
nodes in each iteration of the outer loop, we ensure we're handling one complete level at a time. -
The zigzag twist: Instead of complicating the traversal logic, we keep it simple - always traverse left to right. But we maintain a boolean flag
left
that alternates between true and false for each level. Whenleft
is false, we simply reverse the collected values for that level usingt[::-1]
before adding them to our result. -
Toggle mechanism: The XOR operation
left ^= 1
elegantly flips our boolean flag between 1 and 0 after each level, ensuring the zigzag pattern.
This approach separates concerns beautifully - the BFS handles the traversal mechanics while a simple flag handles the zigzag presentation. We're not zigzagging through the tree; we're zigzagging the output.
Solution Approach
The solution implements BFS with a zigzag modification as described in the reference approach. Let's walk through the implementation step by step:
1. Initial Setup
- Check if the root is
None
. If it is, return an empty list immediately. - Initialize a deque
q
with the root node. A deque is used for efficient O(1) operations at both ends. - Create an empty result list
ans
to store the final zigzag traversal. - Set
left = 1
as our direction flag (1 means left-to-right, 0 means right-to-left).
2. Level-by-Level Processing The main loop continues while the queue is not empty:
while q:
t = [] # Temporary list to store current level's values
for _ in range(len(q)): # Process exactly one level
The key insight here is using range(len(q))
to process exactly the nodes that belong to the current level, even as we're adding new nodes to the queue.
3. Node Processing Within Each Level For each node in the current level:
node = q.popleft() # Remove from front of queue t.append(node.val) # Collect the value if node.left: # Add left child if exists q.append(node.left) if node.right: # Add right child if exists q.append(node.right)
This maintains the left-to-right order for the next level's nodes in the queue.
4. Zigzag Logic Implementation
After collecting all values for the current level in list t
:
ans.append(t if left else t[::-1]) left ^= 1
- If
left
is 1 (true), appendt
as-is (left-to-right order) - If
left
is 0 (false), appendt[::-1]
(reversed, giving right-to-left order) - The XOR operation
left ^= 1
toggles the flag: 1 becomes 0, 0 becomes 1
5. Time and Space Complexity
- Time Complexity: O(n) where n is the number of nodes, as we visit each node exactly once
- Space Complexity: O(w) where w is the maximum width of the tree, which occurs at the level with the most nodes. In the worst case (complete binary tree), this could be O(n/2) = O(n)
The elegance of this solution lies in its simplicity - we don't modify the BFS traversal pattern at all. We always traverse left-to-right, but alternate how we store the results, achieving the zigzag effect with minimal additional logic.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with a small binary tree:
1 / \ 2 3 / \ 4 5
Initial State:
root = Node(1)
q = deque([Node(1)])
ans = []
left = 1
(true, means left-to-right)
Iteration 1 - Level 1:
- Queue has 1 node to process:
[Node(1)]
- Create temporary list:
t = []
- Process Node(1):
node = q.popleft()
→ node = Node(1)t.append(1)
→ t = [1]- Add children:
q.append(Node(2))
,q.append(Node(3))
- Queue now:
[Node(2), Node(3)]
- Since
left = 1
, appendt
as-is:ans = [[1]]
- Toggle flag:
left = 0
(false, means right-to-left)
Iteration 2 - Level 2:
- Queue has 2 nodes to process:
[Node(2), Node(3)]
- Create temporary list:
t = []
- Process Node(2):
node = q.popleft()
→ node = Node(2)t.append(2)
→ t = [2]- Add children:
q.append(Node(4))
,q.append(Node(5))
- Queue now:
[Node(3), Node(4), Node(5)]
- Process Node(3):
node = q.popleft()
→ node = Node(3)t.append(3)
→ t = [2, 3]- No children to add
- Queue now:
[Node(4), Node(5)]
- Since
left = 0
, append reversedt[::-1]
:ans = [[1], [3, 2]]
- Toggle flag:
left = 1
(true, means left-to-right)
Iteration 3 - Level 3:
- Queue has 2 nodes to process:
[Node(4), Node(5)]
- Create temporary list:
t = []
- Process Node(4):
node = q.popleft()
→ node = Node(4)t.append(4)
→ t = [4]- No children to add
- Process Node(5):
node = q.popleft()
→ node = Node(5)t.append(5)
→ t = [4, 5]- No children to add
- Since
left = 1
, appendt
as-is:ans = [[1], [3, 2], [4, 5]]
- Toggle flag:
left = 0
Final Result: [[1], [3, 2], [4, 5]]
The zigzag pattern is achieved:
- Level 1: [1] (left-to-right)
- Level 2: [3, 2] (right-to-left, reversed from original [2, 3])
- Level 3: [4, 5] (left-to-right)
Solution Implementation
1from collections import deque
2from typing import List, Optional
3
4# Definition for a binary tree node.
5class TreeNode:
6 def __init__(self, val=0, left=None, right=None):
7 self.val = val
8 self.left = left
9 self.right = right
10
11class Solution:
12 def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
13 """
14 Performs zigzag level order traversal of a binary tree.
15
16 Args:
17 root: The root node of the binary tree
18
19 Returns:
20 A list of lists containing node values in zigzag level order
21 """
22 # Initialize result list
23 result = []
24
25 # Handle empty tree case
26 if root is None:
27 return result
28
29 # Initialize queue for BFS with root node
30 queue = deque([root])
31
32 # Flag to track traversal direction (True: left-to-right, False: right-to-left)
33 is_left_to_right = True
34
35 # Process nodes level by level
36 while queue:
37 # Store current level's values
38 current_level = []
39
40 # Process all nodes at the current level
41 level_size = len(queue)
42 for _ in range(level_size):
43 # Remove node from front of queue
44 node = queue.popleft()
45 current_level.append(node.val)
46
47 # Add children to queue for next level processing
48 if node.left:
49 queue.append(node.left)
50 if node.right:
51 queue.append(node.right)
52
53 # Add current level to result (reverse if needed for zigzag pattern)
54 if is_left_to_right:
55 result.append(current_level)
56 else:
57 result.append(current_level[::-1])
58
59 # Toggle direction for next level
60 is_left_to_right = not is_left_to_right
61
62 return result
63
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Performs zigzag level order traversal of a binary tree.
19 * Even levels (0-indexed) are traversed left to right,
20 * Odd levels are traversed right to left.
21 *
22 * @param root The root node of the binary tree
23 * @return A list of lists containing node values at each level in zigzag order
24 */
25 public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
26 // Initialize result list to store level-wise node values
27 List<List<Integer>> result = new ArrayList<>();
28
29 // Handle edge case: empty tree
30 if (root == null) {
31 return result;
32 }
33
34 // Use queue for level order traversal (BFS)
35 Deque<TreeNode> queue = new ArrayDeque<>();
36 queue.offer(root);
37
38 // Flag to track traversal direction (true: left-to-right, false: right-to-left)
39 boolean isLeftToRight = true;
40
41 // Process nodes level by level
42 while (!queue.isEmpty()) {
43 // Store current level's node values
44 List<Integer> currentLevel = new ArrayList<>();
45
46 // Process all nodes at current level
47 int levelSize = queue.size();
48 for (int i = 0; i < levelSize; i++) {
49 TreeNode currentNode = queue.poll();
50 currentLevel.add(currentNode.val);
51
52 // Add left child to queue for next level processing
53 if (currentNode.left != null) {
54 queue.offer(currentNode.left);
55 }
56
57 // Add right child to queue for next level processing
58 if (currentNode.right != null) {
59 queue.offer(currentNode.right);
60 }
61 }
62
63 // Reverse the current level list if traversing right to left
64 if (!isLeftToRight) {
65 Collections.reverse(currentLevel);
66 }
67
68 // Add current level to result
69 result.add(currentLevel);
70
71 // Toggle direction for next level
72 isLeftToRight = !isLeftToRight;
73 }
74
75 return result;
76 }
77}
78
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
15 vector<vector<int>> result;
16
17 // Handle empty tree case
18 if (!root) {
19 return result;
20 }
21
22 // Initialize queue for BFS traversal
23 queue<TreeNode*> nodeQueue;
24 nodeQueue.push(root);
25
26 // Flag to track traversal direction (1 = left-to-right, 0 = right-to-left)
27 bool isLeftToRight = true;
28
29 // Process tree level by level
30 while (!nodeQueue.empty()) {
31 vector<int> currentLevel;
32 int levelSize = nodeQueue.size();
33
34 // Process all nodes at current level
35 for (int i = 0; i < levelSize; ++i) {
36 TreeNode* currentNode = nodeQueue.front();
37 nodeQueue.pop();
38
39 // Add current node's value to level result
40 currentLevel.push_back(currentNode->val);
41
42 // Add children to queue for next level processing
43 if (currentNode->left) {
44 nodeQueue.push(currentNode->left);
45 }
46 if (currentNode->right) {
47 nodeQueue.push(currentNode->right);
48 }
49 }
50
51 // Reverse the level values if traversing right-to-left
52 if (!isLeftToRight) {
53 reverse(currentLevel.begin(), currentLevel.end());
54 }
55
56 // Add current level to final result
57 result.push_back(currentLevel);
58
59 // Toggle direction for next level
60 isLeftToRight = !isLeftToRight;
61 }
62
63 return result;
64 }
65};
66
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Performs zigzag level order traversal of a binary tree
17 * @param root - The root node of the binary tree
18 * @returns A 2D array containing node values in zigzag level order
19 */
20function zigzagLevelOrder(root: TreeNode | null): number[][] {
21 const result: number[][] = [];
22
23 // Handle empty tree case
24 if (!root) {
25 return result;
26 }
27
28 // Initialize queue with root node for BFS traversal
29 const currentLevelNodes: TreeNode[] = [root];
30
31 // Flag to track traversal direction (1: left-to-right, 0: right-to-left)
32 let isLeftToRight: number = 1;
33
34 // Process nodes level by level
35 while (currentLevelNodes.length > 0) {
36 // Store values of current level
37 const currentLevelValues: number[] = [];
38
39 // Queue for next level nodes
40 const nextLevelNodes: TreeNode[] = [];
41
42 // Process all nodes in current level
43 for (const node of currentLevelNodes) {
44 // Add current node's value
45 currentLevelValues.push(node.val);
46
47 // Add children to next level queue (left child first, then right)
48 if (node.left) {
49 nextLevelNodes.push(node.left);
50 }
51 if (node.right) {
52 nextLevelNodes.push(node.right);
53 }
54 }
55
56 // Add current level values to result (reverse if right-to-left direction)
57 result.push(isLeftToRight ? currentLevelValues : currentLevelValues.reverse());
58
59 // Replace current level nodes with next level nodes
60 currentLevelNodes.splice(0, currentLevelNodes.length, ...nextLevelNodes);
61
62 // Toggle direction for next level using XOR operation
63 isLeftToRight ^= 1;
64 }
65
66 return result;
67}
68
Time and Space Complexity
The time complexity is O(n)
, where n
is the number of nodes in the binary tree. This is because the algorithm performs a level-order traversal using BFS, visiting each node exactly once. For each node, we perform constant time operations: popping from the queue, appending to the temporary list, and checking/adding children to the queue.
The space complexity is O(n)
, where n
is the number of nodes in the binary tree. The space is used by:
- The queue
q
, which in the worst case (a complete binary tree) can hold up toO(n/2)
nodes at the last level, simplifying toO(n)
- The answer list
ans
, which stores alln
node values across different levels, requiringO(n)
space - The temporary list
t
for each level, which at most containsO(n/2)
nodes for the widest level, simplifying toO(n)
Therefore, the overall space complexity is O(n)
.
Common Pitfalls
1. Incorrectly Processing Multiple Levels Together
A critical mistake is not properly isolating each level's nodes during traversal. If you don't capture the queue size at the start of each level, you'll mix nodes from different levels.
Incorrect Implementation:
while queue: current_level = [] # WRONG: This processes ALL nodes in queue, mixing levels while queue: node = queue.popleft() current_level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(current_level)
Why it fails: After processing the first level, the queue contains nodes from level 2. But as you process level 2 nodes, you're simultaneously adding level 3 nodes to the queue, and the inner while loop will process those too, merging multiple levels into one.
Correct Solution:
while queue:
current_level = []
# Capture the current level size BEFORE processing
level_size = len(queue)
for _ in range(level_size): # Process ONLY current level nodes
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
2. Reversing at the Wrong Time
Another common error is attempting to reverse the order while collecting nodes rather than after collecting all values for a level.
Incorrect Approach:
while queue:
current_level = []
level_size = len(queue)
if not is_left_to_right:
# WRONG: Trying to process nodes in reverse order
for _ in range(level_size):
node = queue.pop() # Taking from the end
current_level.append(node.val)
# This breaks the queue order for next level!
else:
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
Why it fails: This disrupts the queue's order and the BFS traversal pattern. The children won't be added in the correct order for the next level's processing.
Correct Solution: Always traverse left-to-right, but reverse the collected values when needed:
# Always collect in the same order
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# Reverse after collection if needed
if is_left_to_right:
result.append(current_level)
else:
result.append(current_level[::-1])
3. Using Wrong Data Structure
Using a regular list instead of a deque can lead to inefficient operations.
Inefficient:
queue = [root] # Using list while queue: node = queue.pop(0) # O(n) operation!
Efficient:
from collections import deque queue = deque([root]) while queue: node = queue.popleft() # O(1) operation
The pop(0)
operation on a list requires shifting all remaining elements, making it O(n). With deque's popleft()
, it's O(1), significantly improving performance for large trees.
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!