Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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.

  2. 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.

  3. 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. When left is false, we simply reverse the collected values for that level using t[::-1] before adding them to our result.

  4. 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), append t as-is (left-to-right order)
  • If left is 0 (false), append t[::-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 Evaluator

Example 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, append t 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 reversed t[::-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, append t 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 to O(n/2) nodes at the last level, simplifying to O(n)
  • The answer list ans, which stores all n node values across different levels, requiring O(n) space
  • The temporary list t for each level, which at most contains O(n/2) nodes for the widest level, simplifying to O(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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More