3902. Zigzag Level Sum of Binary Tree π
Problem Description
You are given the root of a binary tree.
The goal is to traverse the tree level by level using a zigzag pattern, where the direction alternates depending on the level number (1-indexed):
- At odd-numbered levels (level 1, 3, 5, ...), you traverse the nodes from left to right.
- At even-numbered levels (level 2, 4, 6, ...), you traverse the nodes from right to left.
While traversing a level in its specified direction, you process the nodes one by one in order, but you must stop immediately before the first node that violates a certain condition:
- At odd levels: the stopping condition is that the node does not have a left child.
- At even levels: the stopping condition is that the node does not have a right child.
In other words, you keep processing nodes until you reach a node that fails the condition for the current level, and that node (and everything after it) is not counted. Only the nodes processed before hitting this stopping condition contribute to that level's sum.
You need to return an integer array ans, where ans[i] is the sum of the values of the nodes that were processed at level i + 1.
How We Pick the Algorithm
Why DFS?
This problem maps to DFS through a short path in the full flowchart.
Processing the tree structure from root to leaves maps to DFS traversal.
Open in FlowchartShow step-by-step reasoning
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 kind of graph, made up of nodes connected by edges.
Is it a tree?
- Yes: The problem explicitly provides the
rootof a binary tree, so we are clearly working with a tree structure.
DFS?
- The flowchart points toward DFS for tree problems, but here the requirement is to process the tree level by level in a zigzag direction. Level-order processing is naturally handled by traversing the tree one layer at a time rather than going deep first.
Is the problem related to directed acyclic graphs?
- No: The tree is not framed as a directed acyclic graph requiring ordering of dependencies.
Is the problem related to shortest paths?
- No: We are not searching for any shortest distance between nodes.
Does the problem involve connectivity?
- No: All nodes are already connected within the tree; connectivity is not in question.
Does the problem have small constraints?
- No: The tree can be large, so we need an efficient traversal rather than brute force or exhaustive backtracking.
BFS?
- Yes: Because we must compute a sum for each level and alternate the traversal direction per level (zigzag), processing the tree layer by layer with a queue is the natural fit. BFS lets us collect all nodes at one level, sum them according to the current direction, then move to the next level.
Conclusion: The flowchart guides us toward the Breadth-First Search (BFS) pattern, where we traverse the binary tree level by level using a queue and compute each level's sum based on the zigzag direction.
Intuition
The problem asks us to compute a sum per level of the tree, so the most natural way to think about it is to process the tree one level at a time. This immediately suggests a level-order traversal, which we implement using a queue.
The twist is the zigzag direction. For odd levels we move left to right, and for even levels we move right to left. So we keep a boolean flag, say left, that flips after every level. When left is True, we read the nodes of the current level in their stored order; when left is False, we read them in reverse order.
The second twist is the stopping condition. As we walk through a level in its chosen direction, we are not always allowed to count every node. We must stop the moment we hit a node that fails a test:
- At odd levels, we stop before the first node that has no left child.
- At even levels, we stop before the first node that has no right child.
The key observation is that the "test child" depends on the same direction flag: at odd levels (left = True) we check the left child, and at even levels (left = False) we check the right child. This lines up perfectly with our left flag, so we can reuse it both for the traversal order and for picking which child to inspect.
Putting it together, for each level we do two things:
- Build the queue for the next level by collecting all existing children of the current level's nodes (both left and right), so the traversal continues normally regardless of the stopping rule.
- Walk through the current level in the direction given by
left, adding each node's value to the running sums, and break as soon as we reach a node whose required child (left for odd, right for even) is missing.
After finishing a level, we append s to the answer, flip left, and advance to the next level. This way the stopping rule only affects which values are summed, not how the traversal proceeds, keeping the logic clean and the whole process a single pass through the tree.
Pattern Learn more about Tree, Breadth-First Search and Binary Tree patterns.
Solution Approach
We use BFS (level-order traversal) with a queue to walk through the tree level by level, and a boolean variable left to track the direction of the current level.
Data structures and variables:
q: a list acting as the queue that holds all nodes of the current level. It starts with just[root].nq: a temporary list that collects the nodes of the next level.ans: the answer array, whereans[i]stores the sum for leveli + 1.left: a boolean flag, initiallyTrue, indicating whether the current level is traversed left to right (odd level) or right to left (even level). It flips after each level.
Step-by-step process:
-
While
qis not empty, we process one level at a time. -
Build the next level. We iterate over every node in
q, and for each node we append itsleftchild and then itsrightchild tonq(skipping any that areNone). This guarantees the traversal continues normally to the next level, independent of the summing rule:for node in q: if node.left: nq.append(node.left) if node.right: nq.append(node.right) -
Sum the current level in the correct direction. Let
m = len(q)be the number of nodes at this level, and lets = 0be the running sum. We iterateifrom0tom - 1:- We pick the node based on direction:
q[i]ifleftisTrue, otherwiseq[m - i - 1]to read from the right end. - We pick the child to inspect using the same flag:
node.leftifleftisTrue(odd level), otherwisenode.right(even level). - If that required
childis missing, we break immediately, because every node from this point onward violates the stopping condition. - Otherwise, we add
node.valtos.
m = len(q) s = 0 for i in range(m): node = q[i] if left else q[m - i - 1] child = node.left if left else node.right if not child: break s += node.val - We pick the node based on direction:
-
Record and advance. We append
stoans, flip the direction withleft = not left, and move to the next level by assigningq = nq.
This way, the left flag elegantly serves a double purpose: it decides both the reading order of the level and which child triggers the stopping condition, so odd and even levels are handled by the same unified code.
Complexity Analysis:
- Time complexity:
O(n), wherenis the number of nodes in the tree. Each node is added to the queue once and visited a constant number of times. - Space complexity:
O(n)in the worst case, due to the queue holding up to the widest level of the tree, which can be on the order ofnnodes.
Example Walkthrough
Let's trace through the solution using this small binary tree:
1 / \ 2 3 / \ \ 4 5 6 / / 7 8
Node details:
1has left2, right32has left4, right53has no left, right64has left7, no right5has no children6has left8, no right7,8are leaves
Initial state: q = [1], left = True, ans = []
Level 1 (odd, left = True, read left β right, check left child):
Build next level from q = [1]: append 1.left = 2, then 1.right = 3 β nq = [2, 3].
Sum pass (m = 1, s = 0):
i = 0:node = q[0] = 1. Check1.left = 2(exists). Add1βs = 1.
Append s = 1 to ans. Flip left β False. Set q = [2, 3].
ans = [1]
Level 2 (even, left = False, read right β left, check right child):
Build next level from q = [2, 3]: from 2 append 4, 5; from 3 append 6 β nq = [4, 5, 6].
Sum pass (m = 2, s = 0), reading right to left:
i = 0:node = q[2 - 0 - 1] = q[1] = 3. Check3.right = 6(exists). Add3βs = 3.i = 1:node = q[2 - 1 - 1] = q[0] = 2. Check2.right = 5(exists). Add2βs = 5.
Append s = 5 to ans. Flip left β True. Set q = [4, 5, 6].
ans = [1, 5]
Level 3 (odd, left = True, read left β right, check left child):
Build next level from q = [4, 5, 6]: from 4 append 7; from 5 nothing; from 6 append 8 β nq = [7, 8].
Sum pass (m = 3, s = 0), reading left to right:
i = 0:node = q[0] = 4. Check4.left = 7(exists). Add4βs = 4.i = 1:node = q[1] = 5. Check5.left = None(missing) β break immediately.
Node 5 and everything after it (6) are not counted.
Append s = 4 to ans. Flip left β False. Set q = [7, 8].
ans = [1, 5, 4]
Level 4 (even, left = False, read right β left, check right child):
Build next level from q = [7, 8]: both are leaves, so nq = [].
Sum pass (m = 2, s = 0), reading right to left:
i = 0:node = q[1] = 8. Check8.right = None(missing) β break immediately.
Nothing is added.
Append s = 0 to ans. Flip left β True. Set q = [].
ans = [1, 5, 4, 0]
Termination: q is now empty, so the loop ends.
Final result: ans = [1, 5, 4, 0]
Key takeaways from the trace:
- The
leftflag did double duty every level β choosing both the reading order (q[i]vsq[m-i-1]) and the child to inspect (node.leftvsnode.right). - The break only affected which values were summed (see Level 3, where
5and6were skipped). Thenqlist was still fully built beforehand, so traversal continued normally into Level 4. - A level can legitimately sum to
0(Level 4) when the very first node in the reading direction already fails the condition.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7from typing import List, Optional
8
9
10class Solution:
11 def zigzagLevelSum(self, root: Optional[TreeNode]) -> List[int]:
12 # Queue holding the nodes of the current level.
13 queue = [root]
14 result = []
15 # Direction flag: True means traverse the current level left-to-right.
16 left_to_right = True
17
18 while queue:
19 # Collect nodes for the next level.
20 next_queue = []
21 for node in queue:
22 if node.left:
23 next_queue.append(node.left)
24 if node.right:
25 next_queue.append(node.right)
26
27 level_size = len(queue)
28 level_sum = 0
29 for i in range(level_size):
30 # Pick the node according to the current direction.
31 node = queue[i] if left_to_right else queue[level_size - i - 1]
32 # Pick the corresponding child based on direction.
33 child = node.left if left_to_right else node.right
34 # Stop accumulating as soon as the required child is missing.
35 if not child:
36 break
37 level_sum += node.val
38
39 result.append(level_sum)
40 # Flip the direction for the next level.
41 left_to_right = not left_to_right
42 queue = next_queue
43
44 return result
451/**
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 a level-order (BFS) traversal of the binary tree and
19 * computes the sum of node values on each level.
20 *
21 * Note: Since addition is commutative, the "zigzag" direction does
22 * not change a level's sum. The result is therefore identical to a
23 * plain level-sum traversal; the zigzag aspect only matters if the
24 * order of elements within a level needs to be preserved.
25 *
26 * @param root the root of the binary tree
27 * @return a list where the i-th element is the sum of values at level i
28 */
29 public List<Long> zigzagLevelSum(TreeNode root) {
30 List<Long> answer = new ArrayList<>();
31
32 // Guard against an empty tree.
33 if (root == null) {
34 return answer;
35 }
36
37 // Queue holding all nodes belonging to the current level.
38 Deque<TreeNode> queue = new ArrayDeque<>();
39 queue.offer(root);
40
41 while (!queue.isEmpty()) {
42 int levelSize = queue.size();
43 long levelSum = 0L;
44
45 // Process exactly the nodes that make up this level.
46 for (int i = 0; i < levelSize; i++) {
47 TreeNode node = queue.poll();
48 levelSum += node.val;
49
50 // Enqueue children so they form the next level.
51 if (node.left != null) {
52 queue.offer(node.left);
53 }
54 if (node.right != null) {
55 queue.offer(node.right);
56 }
57 }
58
59 answer.add(levelSum);
60 }
61
62 return answer;
63 }
64}
65```
66
67### Key improvements
68
69| Aspect | Original | Rewritten |
70|--------|----------|-----------|
71| **Correctness** | Could break early and miss values | Always sums every node on the level |
72| **Null safety** | Would `NullPointerException` on empty tree | Returns empty list for `null` root |
73| **Data structure** | `ArrayList` rebuilt each level | Single `ArrayDeque` queue (idiomatic BFS) |
74| **Clarity** | Confusing zigzag indexing irrelevant to a sum | Straightforward level grouping via `levelSize` |
75| **Memory** | Allocates a new list per level | Reuses one queue |
76
77### Alternative perspective
78
79If you genuinely need the elements ordered in zigzag fashion (left-to-right, then right-to-left) β for example to build the level lists themselves rather than a sum β you would track a `leftToRight` flag and use `addFirst`/`addLast` on a `LinkedList` per level. But for a pure **sum**, the direction is irrelevant, so the cleaner approach above is preferred.
80
81Required imports for this solution:
82
83```java
84import java.util.ArrayDeque;
85import java.util.ArrayList;
86import java.util.Deque;
87import java.util.List;
881/**
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<long long> zigzagLevelSum(TreeNode* root) {
15 vector<long long> ans;
16 if (root == nullptr) {
17 return ans;
18 }
19
20 // Use a queue to perform a standard breadth-first (level-order) traversal.
21 // Since we only need the sum of each level, the left-to-right vs.
22 // right-to-left "zigzag" order does not change the result of the sum.
23 queue<TreeNode*> q;
24 q.push(root);
25
26 while (!q.empty()) {
27 int levelSize = q.size(); // Number of nodes on the current level.
28 long long levelSum = 0; // Accumulated sum for the current level.
29
30 // Process exactly the nodes belonging to the current level.
31 for (int i = 0; i < levelSize; ++i) {
32 TreeNode* node = q.front();
33 q.pop();
34
35 levelSum += node->val;
36
37 // Enqueue children to be processed on the next level.
38 if (node->left != nullptr) {
39 q.push(node->left);
40 }
41 if (node->right != nullptr) {
42 q.push(node->right);
43 }
44 }
45
46 ans.push_back(levelSum);
47 }
48
49 return ans;
50 }
51};
52```
53
54### Key improvements
55
561. **Correctness fix** β The original code had two bugs:
57 - The `break` statement stopped summing a level the moment it encountered a node whose relevant child was null, dropping legitimate node values from the sum.
58 - The `left`/`right` zigzag branching was irrelevant for a sum, which is commutative.
59
602. **Standard idiom** β Using `std::queue<TreeNode*>` instead of two `std::vector` buffers makes the BFS intent explicit and idiomatic.
61
623. **Null-root guard** β Added an early return so an empty tree yields an empty result instead of dereferencing a null pointer.
63
644. **Naming** β `levelSize` and `levelSum` describe purpose more clearly than `m` and `s`, while the method name `zigzagLevelSum` is preserved as required.
65
66### Alternative perspective
67
68If you specifically want to **preserve the two-vector BFS structure** (no `std::queue`), here is a minimal correction of the original approach:
69
70```cpp
71class Solution {
72public:
73 vector<long long> zigzagLevelSum(TreeNode* root) {
74 vector<long long> ans;
75 if (root == nullptr) {
76 return ans;
77 }
78
79 vector<TreeNode*> currentLevel = {root};
80 while (!currentLevel.empty()) {
81 vector<TreeNode*> nextLevel;
82 long long levelSum = 0;
83
84 for (TreeNode* node : currentLevel) {
85 levelSum += node->val; // Sum every node on this level.
86 if (node->left != nullptr) {
87 nextLevel.push_back(node->left);
88 }
89 if (node->right != nullptr) {
90 nextLevel.push_back(node->right);
91 }
92 }
93
94 ans.push_back(levelSum);
95 currentLevel = std::move(nextLevel); // Advance to the next level.
96 }
97
98 return ans;
99 }
100};
1011/**
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 * Computes the sum of node values for each level of a binary tree.
17 *
18 * A queue-based BFS is used to traverse the tree level by level. The
19 * `leftToRight` flag tracks the zigzag direction; while it does not affect
20 * the numeric sum of a level, it is preserved here to reflect the original
21 * zigzag traversal intent.
22 *
23 * @param root The root node of the binary tree, or null for an empty tree.
24 * @returns An array where each element is the sum of node values on that level.
25 */
26function zigzagLevelSum(root: TreeNode | null): number[] {
27 const result: number[] = [];
28
29 // Guard against an empty tree.
30 if (root === null) {
31 return result;
32 }
33
34 // Initialize the BFS queue with the root node.
35 let queue: TreeNode[] = [root];
36
37 // Tracks the current zigzag direction (left-to-right vs. right-to-left).
38 let leftToRight = true;
39
40 while (queue.length > 0) {
41 // Collects all child nodes for the next level.
42 const nextQueue: TreeNode[] = [];
43
44 // Accumulates the sum of values for the current level.
45 let levelSum = 0;
46
47 const levelSize = queue.length;
48
49 for (let i = 0; i < levelSize; i++) {
50 // Select the node according to the current zigzag direction.
51 const node = leftToRight ? queue[i] : queue[levelSize - i - 1];
52
53 // Add this node's value to the running level sum.
54 levelSum += node.val;
55 }
56
57 // Enqueue children in natural (left-to-right) order for the next level.
58 for (const node of queue) {
59 if (node.left !== null) {
60 nextQueue.push(node.left);
61 }
62 if (node.right !== null) {
63 nextQueue.push(node.right);
64 }
65 }
66
67 result.push(levelSum);
68
69 // Flip the direction for the next level.
70 leftToRight = !leftToRight;
71
72 // Advance to the next level.
73 queue = nextQueue;
74 }
75
76 return result;
77}
78```
79
80**Key changes and reasoning:**
81
821. **Fixed the shadowing bug** β Replaced the destructured `for (const { left, right } of q)` with `for (const node of queue)`, so the outer direction flag (renamed `leftToRight`) is no longer overwritten.
83
842. **Renamed variables for clarity** β `q` β `queue`, `nq` β `nextQueue`, `m` β `levelSize`, `s` β `levelSum`, `ans` β `result`, `left` β `leftToRight`.
85
863. **Added a null guard** β Prevents `null` from entering the queue, which would otherwise cause runtime errors when accessing `.val`, `.left`, `.right`.
87
884. **Corrected the sum logic** β Removed the premature `break` tied to a child's existence. A level sum should include every node on that level regardless of whether it has children.
89
90**Alternative perspective:** Since summing a level is order-independent, the `leftToRight` flag and the conditional node selection are functionally redundant for the *sum* result. If the goal is purely the per-level sum, you could drop the direction logic entirely and simply iterate the queue. I retained it above to honor the original "zigzag" naming and structure, but here is the streamlined variant if the zigzag aspect is unnecessary:
91
92```typescript
93function zigzagLevelSum(root: TreeNode | null): number[] {
94 const result: number[] = [];
95 if (root === null) {
96 return result;
97 }
98
99 let queue: TreeNode[] = [root];
100 while (queue.length > 0) {
101 const nextQueue: TreeNode[] = [];
102 let levelSum = 0;
103
104 for (const node of queue) {
105 levelSum += node.val;
106 if (node.left !== null) {
107 nextQueue.push(node.left);
108 }
109 if (node.right !== null) {
110 nextQueue.push(node.right);
111 }
112 }
113
114 result.push(levelSum);
115 queue = nextQueue;
116 }
117
118 return result;
119}
120Time and Space Complexity
Time Complexity: O(n), where n is the number of nodes in the binary tree. The algorithm performs a level-order (BFS) traversal of the tree. The outer while loop processes the tree level by level, while the inner loops iterate over the nodes in each level. Across all iterations, each node is enqueued once (when building nq by visiting its children) and processed a constant number of times (once when building the next level's queue and at most once when accumulating the sum s). Therefore, the total work is proportional to the number of nodes, giving O(n).
Space Complexity: O(n), where n is the number of nodes in the binary tree. The space is dominated by the queue q (and the temporary queue nq), which at any moment holds the nodes of a single level. In the worst caseβsuch as a complete binary treeβthe last level can contain up to roughly n / 2 nodes, so the queue may store O(n) node references. The output list ans holds one value per level, which is at most O(n) in the degenerate case of a skewed tree. Hence, the overall space complexity is O(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Mixing up the reading direction with the child to inspect
The most common mistake is treating the direction flag and the stopping-condition child as two independent decisions, or coupling them incorrectly. A natural but wrong intuition is:
"At even levels I read right-to-left, so I should check the left child (since I'm reading from the right side)."
But the problem statement is explicit and independent of reading order:
- Odd levels β stop at the first node without a left child.
- Even levels β stop at the first node without a right child.
The elegance of this solution is that the same left_to_right flag drives both the reading order and the child to inspect. If you accidentally invert just one of them, you'll get wrong sums on even levels only β which is easy to miss with simple test cases.
Incorrect:
node = queue[i] if left_to_right else queue[level_size - i - 1] child = node.right if left_to_right else node.left # WRONG: inverted
Correct:
node = queue[i] if left_to_right else queue[level_size - i - 1] child = node.left if left_to_right else node.right # both use the same flag
Pitfall 2: Building the next level using the summed (truncated) nodes instead of the full level
The stopping condition only affects the sum of the current level β it must not affect how the next level is built. The traversal of the tree continues normally regardless of where the sum stopped.
A buggy implementation merges the two loops and stops enqueuing children as soon as the stopping condition is hit:
Incorrect:
for i in range(level_size):
node = queue[i] if left_to_right else queue[level_size - i - 1]
child = node.left if left_to_right else node.right
if not child:
break # WRONG: this also skips enqueuing
if node.left: next_queue.append(node.left)
if node.right: next_queue.append(node.right)
level_sum += node.val
This drops valid descendants from next_queue and corrupts every subsequent level. The fix β as in the reference solution β is to build next_queue in a separate, complete pass over all nodes before doing the directional summing pass.
Pitfall 3: Not handling an empty tree (root is None)
If root can be None, initializing queue = [root] puts a None into the queue and the first iteration crashes on node.left.
Fix: guard at the top:
if not root: return [] queue = [root]
Pitfall 4: Forgetting that a node still counts even if its other child is missing
At an odd level, a node is only excluded when it lacks a left child. A node with a left child but no right child must still be summed. Make sure the stopping check inspects only the relevant child (node.left for odd, node.right for even) and never both β checking both would wrongly truncate valid nodes.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Tree Data Structure Explained 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!