A clear explanation of zigzag level order traversal using breadth-first search and alternating level direction.
Problem Restatement
We are given the root of a binary tree.
We need to return the values level by level. The first level is read from left to right, the second level from right to left, the third level from left to right, and so on.
The official problem asks for the zigzag level order traversal of a binary tree’s node values.
For this tree:
3
/ \
9 20
/ \
15 7The normal level order traversal would be:
[[3], [9, 20], [15, 7]]The zigzag level order traversal is:
[[3], [20, 9], [15, 7]]The second level is reversed.
Input and Output
| Item | Meaning |
|---|---|
| Input | root, the root node of a binary tree |
| Output | A list of lists of integers |
| First level | Left to right |
| Second level | Right to left |
| Later levels | Keep alternating direction |
| Empty tree | Return [] |
LeetCode gives the TreeNode class:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = rightThe function shape is:
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> list[list[int]]:
...Examples
Consider this tree:
3
/ \
9 20
/ \
15 7Level 0 is read left to right:
[3]Level 1 is read right to left:
[20, 9]Level 2 is read left to right:
[15, 7]So the answer is:
[[3], [20, 9], [15, 7]]For a single-node tree:
1The answer is:
[[1]]For an empty tree, the answer is:
[]First Thought: Use Normal Level Order Traversal
The problem still asks for values grouped by level.
That means breadth-first search is the natural traversal method.
The only difference from normal level order traversal is the order of values inside each level.
We can still visit nodes left to right with a queue. After collecting one level, we decide whether to keep it as is or reverse it.
Key Insight
The traversal order and output order can be handled separately.
The queue should always process nodes in normal left-to-right order:
node.left before node.rightThis keeps the next level organized correctly.
Then, for each level, use a boolean flag:
left_to_right = TrueIf left_to_right is true, append the level values normally.
If it is false, append the reversed level values.
After each level, flip the flag.
Algorithm
If root is None, return [].
Otherwise:
- Create a queue containing
root. - Create an empty answer list.
- Set
left_to_right = True. - While the queue has nodes:
- Create an empty list for the current level.
- Read the current queue size.
- Process exactly that many nodes.
- Append each node value to the current level list.
- Add the left child if it exists.
- Add the right child if it exists.
- If the direction is right to left, reverse the level list.
- Append the level list to the answer.
- Flip the direction.
- Return the answer.
Correctness
The queue begins with the root, so the first processed group is level 0.
At the start of each outer loop, the queue contains exactly the nodes of the current level in left-to-right order. We store the queue size before processing the level, so nodes added during this loop are saved for the next level.
For every node, the algorithm adds the left child before the right child. Therefore, the next level is also stored in left-to-right order in the queue.
The algorithm then uses left_to_right to choose the output order for the current level. On even-numbered levels, it keeps the collected values in left-to-right order. On odd-numbered levels, it reverses them, producing right-to-left order.
After each level, the flag is flipped. Therefore, the output direction alternates on every level.
So every level is included exactly once, every node value appears in the correct level, and the direction alternates as required.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each node is processed once |
| Space | O(n) | The queue and output can store up to n values |
Here, n is the number of nodes in the tree.
Reversing a level costs time proportional to that level’s size. Across the whole tree, all reversals together still cost O(n).
Implementation
from collections import deque
from typing import Optional
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> list[list[int]]:
if root is None:
return []
ans = []
q = deque([root])
left_to_right = True
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left is not None:
q.append(node.left)
if node.right is not None:
q.append(node.right)
if not left_to_right:
level.reverse()
ans.append(level)
left_to_right = not left_to_right
return ansCode Explanation
We first handle the empty tree:
if root is None:
return []Then we create the queue:
q = deque([root])The direction flag starts as True because the first level is read left to right:
left_to_right = TrueThe outer loop processes one level at a time:
while q:For each level, we collect values in normal left-to-right order:
level = []This loop runs only for the nodes already in the current level:
for _ in range(len(q)):Each node is removed from the front of the queue:
node = q.popleft()Then its value is stored:
level.append(node.val)The children are added from left to right:
if node.left is not None:
q.append(node.left)
if node.right is not None:
q.append(node.right)If the current level should be read right to left, we reverse the collected values:
if not left_to_right:
level.reverse()Then we store the level and flip the direction:
ans.append(level)
left_to_right = not left_to_rightFinally:
return ansTesting
from collections import deque
from typing import Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> list[list[int]]:
if root is None:
return []
ans = []
q = deque([root])
left_to_right = True
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left is not None:
q.append(node.left)
if node.right is not None:
q.append(node.right)
if not left_to_right:
level.reverse()
ans.append(level)
left_to_right = not left_to_right
return ans
def run_tests():
s = Solution()
root1 = TreeNode(
3,
TreeNode(9),
TreeNode(20, TreeNode(15), TreeNode(7)),
)
assert s.zigzagLevelOrder(root1) == [[3], [20, 9], [15, 7]]
root2 = TreeNode(1)
assert s.zigzagLevelOrder(root2) == [[1]]
root3 = None
assert s.zigzagLevelOrder(root3) == []
root4 = TreeNode(
1,
TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, TreeNode(6), TreeNode(7)),
)
assert s.zigzagLevelOrder(root4) == [[1], [3, 2], [4, 5, 6, 7]]
root5 = TreeNode(
1,
TreeNode(2, TreeNode(3, TreeNode(4))),
None,
)
assert s.zigzagLevelOrder(root5) == [[1], [2], [3], [4]]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[3,9,20,null,null,15,7] | Standard example |
| Single node | Minimum non-empty tree |
| Empty tree | Confirms empty output |
| Complete tree | Confirms alternating order across full levels |
| Left-skewed tree | Confirms one-node levels still work |