ZigZag ⚡ traverse that binary tree 🌲

Today we are going to solve two DS problems that are actually very similar. They are respectively :-
And I like to solve them in a specific way which comes natural to me.
Binary Tree Level Order Traversal

Here is the code which works :-
var levelOrder = function(root) {
if(!root)
return [];
let currentQueue = [];
let nextNodes = [];
const output = [[root.val]];
currentQueue.push(root);
while(currentQueue.length){
const node = currentQueue.shift();
nextNodes = nextNodes.concat(findNextNodes(node));
if(!currentQueue.length && nextNodes.length){
currentQueue = nextNodes;
nextNodes = [];
output.push(currentQueue.map(node=>node.val));
}
}
return output;
};
const findNextNodes=(node)=>{
const nextNodes = [];
if(node.left){
nextNodes.push(node.left);
}
if(node.right){
nextNodes.push(node.right);
}
return nextNodes;
}
Explanation :-
- First we check whether
rootof binary tree exists or not. If not, simply return empty array[]. - Initialize a
currentQueueandnextNodesarray. - Initialize a
output2D array where for each level we maintain an array consisting of the level nodes. Initially, the[root.val]will be the first valid entry. Sooutputwill be[[3]]. - And then push the current
rootnode insidecurrentQueue. SocurrentQueuewill be[Node(3)]. - Now, we will loop over the
currentQueueuntil it is empty. - Being a queue, we dequeue the first entry in it using
shiftoperator oncurrentQueue. - Now, we pass this entry or
node(Node(3)) insidefindNextNodesfunction whose purpose it to return us a list of children of the passednode. findNextNodesinitializes an emptynextNodesarray and pushes theleftandrightchildren ofnodeif they exist and returns thenextNodesarray.nextNodeswill be[Node(9),Node(20)].- Then the returned array is concatenated with
nextNodes.nextNodeswill be[Node(9),Node(20)]. - Now before the end of the loop, if
currentQueueis empty andnextNodesis not,currentQueuestarts referring thenextNodesarray andnextNodesis reinitialized to[]. Also, all thenode.valvalues in thecurrentQueueare pushed in the form of an array tooutput. By this step, output will be[[3],[9,20]]. - This way the for each level we will successfully obtain the left to right level order traversal in the
outputarray.
Binary Tree Zigzag Level Order Traversal

Before we see the code and explanation, it's important to note that we will be converting the above solution to obtain the current one. I would like to stress on this derivation because this is how I particularly enjoyed the process of arriving at this solution. We will do it in two iterations so that the process of arriving at this solution seems more intuitive.
Alright, here is the first iteration :-
var zigzagLevelOrder = function(root) {
if(!root)
return [];
let currentQueue = [];
let nextNodes = [];
let isNextLevelEven = true;
const output = [[root.val]];
currentQueue.push(root);
while(currentQueue.length){
const node = currentQueue.shift();
nextNodes = nextNodes.concat(findNextNodes(node,isNextLevelEven));
if(!currentQueue.length && nextNodes.length){
currentQueue = nextNodes;
nextNodes = [];
isNextLevelEven = !isNextLevelEven;
output.push(currentQueue.map(node=>node.val));
}
}
return output;
};
const findNextNodes=(node,isNextLevelEven)=>{
const nextNodes = [];
if(node.left){
nextNodes.push(node.left);
}
if(node.right){
nextNodes.push(node.right);
}
return isNextLevelEven?nextNodes.reverse():nextNodes;
}
Explanation :-
- As soon as you see the above code, you will realize that all it does is add subtle nuances to level order traversal's code.
- The first nuance is the
isNextLevelEvenvariable. So let's discuss it. When we think about a zigzag traversal, it starts on level 1 i.e.rootnode from left to right and then on level 2 from right to left and the again from left to right on level 3. So there is a alternating state that's being introduced. Alternating state can be depicted using a boolean and that's what we are doing here. Ifrootis level 1 i.e. an odd level, then the next level would be even. This is why I have initializedisNextLevelEventotruein the starting of this code. - Before the start of the loop,
outputwill be[[3]]andcurrentQueuewill be[Node(3)]. - Now, we will loop over the
currentQueueuntil it is empty. - Being a queue, we dequeue the first entry in it using
shiftoperator oncurrentQueue. - Now, we pass
node(Node(3)) andisNextLevelEveninsidefindNextNodesfunction whose purpose it to return us a list of children of the passednodeaccording toisNextLevelEvenboolean variable. findNextNodeslike before initializes an emptynextNodesarray and pushes theleftandrightchildren ofnodeif they exist. The change comes in the returning value step where now exists a ternary condition whereisNextLevelEvenif true will return the reversednextNodes(for right to left) array else thenextNodes(for left to right) as it is . In this step, sinceisNextLevelEvenistrue, the returned value will be[Node(20),Node(9)].- Then the returned array is concatenated with
nextNodes.nextNodeswill be[Node(20),Node(9)]. - Now before the end of the loop, if
currentQueueis empty andnextNodesis not,currentQueuestarts referring thenextNodesarray andnextNodesis reinitialized to[]. We also flip theisNextLevelEvenboolean. Also, all thenode.valvalues in thecurrentQueueare pushed in the form of an array tooutput. By this step, output will be[[3],[20,9]]. - The algorithm seems reasonable till here. But let's see what happens in next iteration of while loop.
- Now
currentQueuebeing[Node(20),Node(9)],nodewill beNode(20)due toshiftoperation. - Now since next level is 3 which is odd,
[Node(15),Node(7)]will be concatenated withnextNodes. - And again in next iteration of while loop,
nodewill beNode(9)due toshiftoperation resulting innextNodesbeing[Node(15),Node(7),Node(23),Node(43)]. - Now when the
ifcondition inside thewhileloop holds true,outputwill become[[3],[20,9],[15,7,23,43]]which clearly is wrong because the last level is not traversed from left to right as we wanted to.
The step where we went wrong here is assuming that currentQueue well has to be a queue. Being a queue, we will never be able to start a traversal from the last element. For instance, after we got correct output till level 2, we would have wanted, that for level 3, first the children of Node(9) were pushed from left to right and then for Node(20) but instead the reverse happened. This is literally how I formed the second intuition that it's my last element from which the next level of nodes should be traversed. And the Data Structure which operates primarily on the last element is none other than a stack.
So all we need is to replace the shift operation with pop and things will work as we intend to. Obviously, it's better to rename currentQueue to currentStack for brevity.
So here is the working code :-
var zigzagLevelOrder = function(root) {
if(!root)
return [];
let currentStack = [];
let nextNodes = [];
let isNextLevelEven = true;
const output = [[root.val]];
currentStack.push(root);
while(currentStack.length){
const node = currentStack.pop();
nextNodes = nextNodes.concat(findNextNodes(node,isNextLevelEven));
if(!currentStack.length && nextNodes.length){
currentStack = nextNodes;
nextNodes = [];
isNextLevelEven = !isNextLevelEven;
output.push(currentStack.map(node=>node.val));
}
}
return output;
};
const findNextNodes=(node,isNextLevelEven)=>{
const nextNodes = [];
if(node.left){
nextNodes.push(node.left);
}
if(node.right){
nextNodes.push(node.right);
}
return isNextLevelEven?nextNodes.reverse():nextNodes;
}
The second question was asked to me in a startup's interview in early stage of my career. That time I didn't know JS and was interviewing for a Java Dev position. I didn't do well in that interview and kind of dreaded this problem. Later one day while solving the level order traversal on leetcode, I realised that the zigzag problem is an extension of this and then implemented it in same manner as I documented in this article. That connection between both problems was cool to arrive at.




