Problem overview
The method zigzagTraversal receives the root of a binary tree (a TreeNode | null) and returns the values level by level as a number[][]. The first level is read left to right, the next one right to left, and the direction keeps alternating.
For the tree built from [3, 9, 20, null, null, 15, 7], the root 3 has children 9 and 20, and 20 has children 15 and 7. The levels are [3], then [20, 9] and then [15, 7], so the result is [[3], [20, 9], [15, 7]].
Approach: let the stacks do the reversing
A stack gives items back in the opposite order from the one in which they went in. That is exactly the reversal the zigzag needs, so there is no need to reverse any list afterwards.
Use two stacks. currentLevelStack holds the nodes of the level being read, and each pop gives the next node to record. nextLevelStack collects the children of the nodes just read. Which child is pushed first decides the direction the next level will be popped:
- On an even level, the next level must come out right to left. Push the left child first and the right child second, so the right child is on top.
- On an odd level, the next level must come out left to right. Push the right child first, so the left child is on top.
When the current stack runs empty, the level is finished and the next stack holds the whole next level. The two swap roles and the level number goes up. Only two stacks are alive at any time.
For the example, level 0 pops 3 and pushes 9, then 20. Level 1 pops 20 first, then 9; reading 20 (odd level) pushes its children right first, so 15 ends up on top. Level 2 therefore pops 15, then 7.
Step by step
- Create the result, an empty
number[][]with one inner array per level. - Create the two stacks as
TreeNode[]arrays, declared withletbecause they are reassigned when a level ends. - Push the root through a helper,
addToStack, that skipsnullvalues. For an empty tree nothing is pushed and the loop never runs. - Track the level number, starting at 0 for the root.
- Loop while the current stack has nodes. A length of 0 counts as false.
- Pop a node. The loop condition guarantees one exists, so the non-null assertion
!is safe. - Record its value with a helper,
addToResult, that creates the level's list the first time the level is reached. - Push the children in the order that suits the level: left then right on even levels.
- Push right then left on odd levels.
- When the current stack is empty, promote the next stack to current, give the next stack a fresh empty array and increase the level.
- Return the result after the loop.
- Write the helpers.
addToResultpushes an empty array whenlevelis at leastresult.length, then pushes the value.addToStackloops over a list of possibly-nullnodes and pushes only the real ones.
The solution in TypeScript
class Solution {
addToStack(stack: TreeNode[], nodes: (TreeNode | null)[]): void {
for (const node of nodes) {
if (node) {
stack.push(node);
}
}
}
addToResult(result: number[][], level: number, value: number): void {
if (level >= result.length) {
result.push([]);
}
result[level].push(value);
}
zigzagTraversal(root: TreeNode | null): number[][] {
const result: number[][] = [];
let currentLevelStack: TreeNode[] = [];
let nextLevelStack: TreeNode[] = [];
this.addToStack(currentLevelStack, [root]);
let level = 0;
while (currentLevelStack.length) {
const node = currentLevelStack.pop()!;
this.addToResult(result, level, node.val);
if (level % 2 === 0) {
this.addToStack(nextLevelStack, [node.left, node.right]);
} else {
this.addToStack(nextLevelStack, [node.right, node.left]);
}
if (currentLevelStack.length === 0) {
currentLevelStack = nextLevelStack;
nextLevelStack = [];
++level;
}
}
return result;
}
}
The TypeScript details worth a closer look:
- The non-null assertion.
pop()has the typeTreeNode | undefined, because an array can be empty. The trailing!tells the compiler that the value is notundefined. It is only a promise to the compiler: it adds no check and no code to the compiled JavaScript, so it's safe only because the loop condition guarantees a node. - Typing the helper's input.
(TreeNode | null)[]is an array whose elements may benull. That lets the caller pass[node.left, node.right]without checking each child, while the stacks themselves stay typedTreeNode[]. - Truthiness in conditions.
while (currentLevelStack.length)works because the number 0 is falsy and any other number is truthy.if (node)filters outnull. letversusconst. The stacks useletbecause the variables are reassigned at the end of a level.resultandnodeuseconst.- Swapping by reassignment.
nextLevelStack = []creates a new array instead of clearing the old one. The old array is nowcurrentLevelStack, so it must not be touched.
Complexity
Let n be the number of nodes.
Time: O(n). Every node is pushed once, popped once and recorded once, and each of those steps is constant work. There is no sorting and no reversal.
Space: O(n). The result holds all values. The two stacks together hold at most two adjacent levels, which is at most n nodes. A wide tree makes them large; a narrow one keeps them small.
Edge cases and common mistakes
- An empty tree.
addToStackpushes nothing, the loop is skipped and the result is[]. - Pushing the children in the wrong order. If you push left then right on every level, the third level of a full seven-node tree would come out as
[5, 4, 7, 6], which is neither direction. The push order must alternate. - Reading the wrong direction. Remember that the order of pushing is the reverse of the order of reading. Pushing for level
L + 1happens while levelLis being read. - Checking the stack before the pop. The end-of-level test must come after the pop and the pushes. At that moment an empty current stack means the last node of the level was just handled.
- Forgetting to reset the next stack. If both variables kept pointing to the same array, nodes of different levels would mix.
- Pushing
nullchildren. Anullon the stack would crash the next pop'snode.val. The filter inaddToStackprevents it.
Testing
[3, 9, 20, null, null, 15, 7]gives[[3], [20, 9], [15, 7]].[1, 2, 3, 4, 5, 6, 7]gives[[1], [3, 2], [4, 5, 6, 7]].- A single node
[1]gives[[1]]. - An empty tree gives
[].