Solution: Binary Tree Level Order Traversal
Let's solve the Binary Tree Level Order Traversal problem using the Tree Breadth-first Search pattern.
Statement
Given the root of a binary tree, display the values of its nodes while performing a level order traversal. Return the node values for all levels in a string separated by the character :
. If the tree is empty, i.e., the number of nodes is , then return “None” as the output.
Constraints:
-
The number of nodes in the tree is in the range .
-
Node.data
Solution
So far, you’ve probably brainstormed some approaches and have an idea of how to solve this problem. Let’s explore some of these approaches and figure out which one to follow based on considerations such as time complexity and any implementation constraints.
Naive approach
The naive approach would be to first calculate the height of the tree and then recursively traverse it, level by level. The printing function would be called for every level in the range .
In the case of a skewed tree, the time complexity for this approach would be , where is the number of nodes. The space complexity is .
Optimized approach using tree breadth-first search
We can traverse a binary tree using the depth or breadth-first search pattern. The fundamental characteristic of the depth-first search pattern is that it travels as far as possible along each tree branch before exploring the others.
However, since we are asked to print all nodes at the same hierarchical level, this problem naturally falls under the tree breadth-first search pattern. It starts searching at the root node and moves down level by level, exploring adjacent nodes at level . We’ll print all the nodes at each level and then move to the next level.
Solution 1
The essence of the algorithm to perform level-order traversal of a binary tree involves using two queues to process nodes level by level, similar to the breadth-first search (BFS) approach. One queue is used to store nodes of the current level, and the other is used to store the nodes of the next level. Starting with the root node in the current queue, each node is dequeued, its value is processed, and its children are added to the next queue. Once all nodes in the current queue are processed, the roles of the two queues are swapped, and the process continues until all levels are traversed.
We can use two queues to solve this problem:
- A current queue
- A next queue
The slides below illustrate how the algorithm runs:
Note: In the following section, we will gradually build the solution. Alternatively, you can skip straight to just the code.
Step-by-step solution construction
-
First, we’ll declare an array of two queues. This step will help us swap values between our two queues later.
-
We then declare and point
currentQueue
to the index andnextQueue
to the index of thequeues
array. -
We’ll start by pushing the root node to
currentQueue
and setting the tree level to zero.
class Solution {public static void printQueue(Queue<TreeNode<Integer>> copy){TreeNode<Integer> node=null;Iterator<TreeNode<Integer>> through = copy.iterator() ;while(through.hasNext() ) {node = through.next();System.out.print("["+ node.data+ "]") ;}System.out.println() ;}public static void levelOrderTraversal(TreeNode<Integer> root) {// We print null if the root is nullif (root == null) {System.out.print("null");} else {System.out.println("\n\tInitializing the queues");// Declaring an array of two queuesArrayList<Queue<TreeNode<Integer>>> queues = new ArrayList<Queue<TreeNode<Integer>>>();queues.add(new ArrayDeque<TreeNode<Integer>>());queues.add(new ArrayDeque<TreeNode<Integer>>());System.out.println("\t\tqueues array: " + queues);// Initializing the current and next queuesQueue<TreeNode<Integer>> currentQueue = queues.get(0);Queue<TreeNode<Integer>> nextQueue = queues.get(1);System.out.println("\t\tCurrent queue: " + currentQueue);System.out.println("\t\tNext queue: " + nextQueue);// Enqueuing the root node into the current queue and setting// level to zeroSystem.out.println("\t\tEnqueuing the root node into the current queue");currentQueue.add(root);System.out.print("\t\t\tUpdated current queue: ");printQueue(currentQueue);int levelNumber = 0;System.out.println("\t\t\tLevel number: " + levelNumber);}}public static void main(String[] argv) {List<TreeNode<Integer>> testCasesRoots = new ArrayList<>();List<TreeNode<Integer>> input1 = Arrays.asList(new TreeNode<>(100),new TreeNode<>(50),new TreeNode<>(200),new TreeNode<>(25),new TreeNode<>(75),new TreeNode<>(350));BinaryTree<Integer> tree1 = new BinaryTree<>(input1);testCasesRoots.add(tree1.root);List<TreeNode<Integer>> input2 = Arrays.asList(new TreeNode<>(25),new TreeNode<>(50),null,new TreeNode<>(100),new TreeNode<>(200),new TreeNode<>(350));BinaryTree<Integer> tree2 = new BinaryTree<>(input2);testCasesRoots.add(tree2.root);List<TreeNode<Integer>> input3 = Arrays.asList(new TreeNode<>(350),null,new TreeNode<>(100),null,new TreeNode<>(50),new TreeNode<>(25));BinaryTree<Integer> tree3 = new BinaryTree<>(input3);testCasesRoots.add(tree3.root);BinaryTree<Integer> tree4 = new BinaryTree<>(Arrays.asList(new TreeNode<>(100)));testCasesRoots.add(tree4.root);testCasesRoots.add(null);for (int i = 0; i < testCasesRoots.size(); i++) {if (i > 0) {System.out.println("\n");}System.out.println(i + 1 + ".\tBinary Tree");Print.displayTree(testCasesRoots.get(i));System.out.print("\n\tLevel order traversal: ");levelOrderTraversal(testCasesRoots.get(i));System.out.println(new String(new char[100]).replace('\0', '-'));}}}
We’ll carry out the following operations until currentQueue
is empty:
-
Dequeue the first element of
currentQueue
. -
Enqueue its children to
nextQueue
. -
Store the dequeued node in
result
.
class Solution {public static String printQueue(Queue<TreeNode<Integer>> copy) {if (copy != null && !copy.isEmpty()) {StringBuilder output = new StringBuilder("[");for (TreeNode<Integer> through : copy) {output.append(through.data).append(", ");}output.delete(output.length() - 2, output.length());output.append("]");return output.toString();}return "[]";}public static String levelOrderTraversal(TreeNode<Integer> root) {StringBuilder result = new StringBuilder();// Print None if the root doesn't existif (root == null) {System.out.print("None");} else {System.out.println("\n\tInitializing the queues");// Declaring an array of two queuesArrayList<Queue<TreeNode<Integer>>> queues = new ArrayList<Queue<TreeNode<Integer>>>();queues.add(new ArrayDeque<TreeNode<Integer>>());queues.add(new ArrayDeque<TreeNode<Integer>>());System.out.println("\t\tqueues array: " + queues);// Initializing the current and next queuesQueue<TreeNode<Integer>> currentQueue = queues.get(0);Queue<TreeNode<Integer>> nextQueue = queues.get(1);System.out.println("\t\tCurrent queue: " + currentQueue);System.out.println("\t\tNext queue: " + nextQueue);// Enqueuing the root node into the current queue and setting// level to zeroSystem.out.println("\t\tEnqueuing the root node into the current queue");currentQueue.add(root);System.out.print("\t\t\tUpdated current queue: " + printQueue(currentQueue));int levelNumber = 0;System.out.println("\n\t\t\tLevel number: " + levelNumber);System.out.println("\n\tIterating the current queue");int n = 0;while (!currentQueue.isEmpty()) {System.out.println("\t\tLoop iteration " + n);n += 1;// Dequeuing and printing the first element of queueSystem.out.print("\t\t\tDequeuing from the current queue: " + printQueue(currentQueue));TreeNode<Integer> temp = currentQueue.poll();System.out.println(" ⟶ " + printQueue(currentQueue));System.out.println("\t\t\t\tPopped node: " + temp.data);result.append(String.valueOf(temp.data));System.out.println("\t\t\t\tAppending the popped node to the results string: " + result);// Adding dequeued node's children to the next queueSystem.out.println("\t\t\tAdding " + temp.data + "'s children to the next queue");if (temp.left != null) {nextQueue.add(temp.left);System.out.println("\t\t\t\tEnqueuing the left child, "+ temp.left.data);System.out.print("\t\t\t\t\tNext queue: " + printQueue(nextQueue));}if (temp.right != null) {nextQueue.add(temp.right);System.out.println("\n\t\t\t\tEnqueuing the right child, "+ temp.right.data);System.out.print("\t\t\t\t\tNext queue: " + printQueue(nextQueue));}}}return result.toString();}public static void main(String[] argv) {List<TreeNode<Integer>> testCasesRoots = new ArrayList<>();List<TreeNode<Integer>> input1 = Arrays.asList(new TreeNode<>(100),new TreeNode<>(50),new TreeNode<>(200),new TreeNode<>(25),new TreeNode<>(75),new TreeNode<>(350));BinaryTree<Integer> tree1 = new BinaryTree<>(input1);testCasesRoots.add(tree1.root);List<TreeNode<Integer>> input2 = Arrays.asList(new TreeNode<>(25),new TreeNode<>(50),null,new TreeNode<>(100),new TreeNode<>(200),new TreeNode<>(350));BinaryTree<Integer> tree2 = new BinaryTree<>(input2);testCasesRoots.add(tree2.root);List<TreeNode<Integer>> input3 = Arrays.asList(new TreeNode<>(350),null,new TreeNode<>(100),null,new TreeNode<>(50),new TreeNode<>(25));BinaryTree<Integer> tree3 = new BinaryTree<>(input3);testCasesRoots.add(tree3.root);BinaryTree<Integer> tree4 = new BinaryTree<>(Arrays.asList(new TreeNode<>(100)));testCasesRoots.add(tree4.root);testCasesRoots.add(null);for (int i = 0; i < testCasesRoots.size(); i++) {if (i > 0) {System.out.println("\n");}System.out.println(i + 1 + ".\tBinary Tree");Print.displayTree(testCasesRoots.get(i));System.out.print("\n\tLevel order traversal: ");System.out.println(levelOrderTraversal(testCasesRoots.get(i)) + "\n");System.out.println(new String(new char[100]).replace('\0', '-'));}}}
-
The loop in the code above runs once, since we only have one node in
currentQueue
. To ensure all the tree nodes are visited, we’ll swapcurrentQueue
andnextQueue
once the former becomes empty. -
We’ll swap
currentQueue
withnextQueue
and incrementlevelNumber
by . Since we’re moving to the next level, we’ll also append:
toresult
. -
We terminate the loop if
currentQueue
is still empty and return the result.
class Solution {public static String printQueue(Queue<TreeNode<Integer>> copy) {if (copy != null && !copy.isEmpty()) {StringBuilder output = new StringBuilder("[");for (TreeNode<Integer> through : copy) {output.append(through.data).append(", ");}output.delete(output.length() - 2, output.length());output.append("]");return output.toString();}return "[]";}public static String levelOrderTraversal(TreeNode<Integer> root) {// We print null if the root is nullStringBuilder result = new StringBuilder();if (root == null) {System.out.println("None");return "None";} else {System.out.println("\n\tInitializing the queues");// Declaring an array of two queuesArrayList<Queue<TreeNode<Integer>>> queues = new ArrayList<Queue<TreeNode<Integer>>>();queues.add(new ArrayDeque<TreeNode<Integer>>());queues.add(new ArrayDeque<TreeNode<Integer>>());System.out.println("\t\tqueues array: " + queues);// Initializing the current and next queuesQueue<TreeNode<Integer>> currentQueue = queues.get(0);Queue<TreeNode<Integer>> nextQueue = queues.get(1);System.out.println("\t\tCurrent queue: " + currentQueue);System.out.println("\t\tNext queue: " + nextQueue);// Enqueuing the root node into the current queue and setting// level to zeroSystem.out.println("\t\tEnqueuing the root node into the current queue");currentQueue.add(root);System.out.print("\t\t\tUpdated current queue: " + printQueue(currentQueue));int levelNumber = 0;System.out.println("\n\t\t\tLevel number: " + levelNumber);System.out.println("\n\tIterating the current queue");int n = 0;while (!currentQueue.isEmpty()) {System.out.println("\n\t\tLoop iteration " + n);n += 1;// Dequeuing and printing the first element of queueSystem.out.print("\t\t\tDequeuing from the current queue: " + printQueue(currentQueue));TreeNode<Integer> temp = currentQueue.poll();System.out.println(" ⟶ " + printQueue(currentQueue));System.out.println("\t\t\t\tPopped node: " + temp.data);result.append(String.valueOf(temp.data));System.out.println("\t\t\t\tAppending the popped node to the results string: " + result);// Adding dequeued node's children to the next queueSystem.out.println("\t\t\tAdding " + temp.data + "'s children to the next queue");if (temp.left != null) {nextQueue.add(temp.left);System.out.println("\t\t\t\tEnqueuing the left child, " + temp.left.data);System.out.print("\t\t\t\t\tNext queue: " + printQueue(nextQueue));}if (temp.right != null) {nextQueue.add(temp.right);System.out.println("\n\t\t\t\tEnqueuing the right child, " + temp.right.data);System.out.print("\t\t\t\t\tNext queue: " + printQueue(nextQueue));}// When the current queue is empty, we increase the level, print a new line// and swap the current and next queuesSystem.out.print("\n\n\t\t\tCurrent queue: " + printQueue(currentQueue));System.out.print("\n\t\t\tNext queue: " + printQueue(nextQueue));if (currentQueue.isEmpty()) {System.out.println("\n\t\t\tCurrent queue is empty, swapping the queues");++levelNumber;if (!nextQueue.isEmpty()) {result = result.append(" : ");System.out.println("\t\t\t\tMoving to the next level, appending a colon to the result string ⟶ "+ result);}currentQueue = queues.get(levelNumber % 2);nextQueue = queues.get((levelNumber + 1) % 2);System.out.println("\t\t\t\tIncrementing level number: " + (levelNumber - 1) + " ⟶ " + levelNumber);System.out.print("\t\t\t\tCurrent queue after swapping: " + printQueue(currentQueue));System.out.print("\n\t\t\t\tNext queue after swapping: " + printQueue(nextQueue));} else {result = result.append(", ");}}System.out.println("\n\n\tLevel order traversal: " + result);}return result.toString();}public static void main(String[] argv) {List<TreeNode<Integer>> testCasesRoots = new ArrayList<>();List<TreeNode<Integer>> input1 = Arrays.asList(new TreeNode<>(100),new TreeNode<>(50),new TreeNode<>(200),new TreeNode<>(25),new TreeNode<>(75),new TreeNode<>(350));BinaryTree<Integer> tree1 = new BinaryTree<>(input1);testCasesRoots.add(tree1.root);List<TreeNode<Integer>> input2 = Arrays.asList(new TreeNode<>(25),new TreeNode<>(50),null,new TreeNode<>(100),new TreeNode<>(200),new TreeNode<>(350));BinaryTree<Integer> tree2 = new BinaryTree<>(input2);testCasesRoots.add(tree2.root);List<TreeNode<Integer>> input3 = Arrays.asList(new TreeNode<>(350),null,new TreeNode<>(100),null,new TreeNode<>(50),new TreeNode<>(25));BinaryTree<Integer> tree3 = new BinaryTree<>(input3);testCasesRoots.add(tree3.root);BinaryTree<Integer> tree4 = new BinaryTree<>(Arrays.asList(new TreeNode<>(100)));testCasesRoots.add(tree4.root);testCasesRoots.add(null);for (int i = 0; i < testCasesRoots.size(); i++) {if (i > 0) {System.out.println("\n");}System.out.println(i + 1 + ".\tBinary Tree");Print.displayTree(testCasesRoots.get(i));System.out.print("\n\tLevel order traversal: ");System.out.println(levelOrderTraversal(testCasesRoots.get(i)) + "\n");System.out.println(new String(new char[100]).replace('\0', '-'));}}}
Just the code
Here’s the complete solution to this problem:
class Solution {public static String levelOrderTraversal(TreeNode<Integer> root) {StringBuilder result = new StringBuilder();if (root == null) {return "None";} else {ArrayList<Queue<TreeNode<Integer>>> queues = new ArrayList<Queue<TreeNode<Integer>>>();queues.add(new ArrayDeque<TreeNode<Integer>>());queues.add(new ArrayDeque<TreeNode<Integer>>());Queue<TreeNode<Integer>> currentQueue = queues.get(0);Queue<TreeNode<Integer>> nextQueue = queues.get(1);currentQueue.add(root);int levelNumber = 0;while (!currentQueue.isEmpty()) {TreeNode<Integer> temp = currentQueue.poll();result.append(String.valueOf(temp.data));if (temp.left != null) {nextQueue.add(temp.left);}if (temp.right != null) {nextQueue.add(temp.right);}if (currentQueue.isEmpty()) {++levelNumber;if (!nextQueue.isEmpty()) {result = result.append(" : ");}currentQueue = queues.get(levelNumber % 2);nextQueue = queues.get((levelNumber + 1) % 2);} else {result = result.append(", ");}}}return result.toString();}public static void main(String[] argv) {List<TreeNode<Integer>> testCasesRoots = new ArrayList<>();List<TreeNode<Integer>> input1 = Arrays.asList(new TreeNode<>(100),new TreeNode<>(50),new TreeNode<>(200),new TreeNode<>(25),new TreeNode<>(75),new TreeNode<>(350));BinaryTree<Integer> tree1 = new BinaryTree<>(input1);testCasesRoots.add(tree1.root);List<TreeNode<Integer>> input2 = Arrays.asList(new TreeNode<>(25),new TreeNode<>(50),null,new TreeNode<>(100),new TreeNode<>(200),new TreeNode<>(350));BinaryTree<Integer> tree2 = new BinaryTree<>(input2);testCasesRoots.add(tree2.root);List<TreeNode<Integer>> input3 = Arrays.asList(new TreeNode<>(350),null,new TreeNode<>(100),null,new TreeNode<>(50),new TreeNode<>(25));BinaryTree<Integer> tree3 = new BinaryTree<>(input3);testCasesRoots.add(tree3.root);BinaryTree<Integer> tree4 = new BinaryTree<>(Arrays.asList(new TreeNode<>(100)));testCasesRoots.add(tree4.root);testCasesRoots.add(null);for (int i = 0; i < testCasesRoots.size(); i++) {if (i > 0) {System.out.println("\n");}System.out.println(i + 1 + ".\tBinary Tree");Print.displayTree(testCasesRoots.get(i));System.out.print("\n\tLevel order traversal: ");System.out.println(levelOrderTraversal(testCasesRoots.get(i)) + "\n");System.out.println(new String(new char[100]).replace('\0', '-'));}}}
Solution summary
To recap, the solution to this problem can be divided into the following parts:
-
Use two queues,
currentQueue
andnextQueue
, to keep track of the nodes. -
Dequeue nodes from
currentQueue
and push their children to thenextQueue
. -
Store the dequeued nodes in
result
. -
If the former queue is empty in step 3, swap the two queues and increment the level number.
-
Return
result
once the loop terminates.
Time complexity
The time complexity of this solution is linear, , where is the number of nodes, because every node is visited and printed only once.
Space complexity
The space complexity of this solution is linear, , since the algorithm instantiates queues that take up space of up to nodes. This is because the maximum queue size occurs at the level of the leaf nodes of a balanced binary tree.
Solution 2
In the previous solution, we used two queues to keep track of the current level. However, there is an alternative approach to conduct level order traversal on a binary tree using a single deque. It efficiently traverses the tree by processing nodes at each level in a breadth-first manner. By iteratively dequeuing nodes and collecting their data values, it constructs a hierarchical representation of the tree.
The steps for this solution are as follows:
-
Initialize an empty list called
result
. This list will hold the data of nodes visited in the level order. -
Check if the tree is empty. If so, we return NULL.
-
Next, create a deque named
current_queue
to facilitate breath-first traversal. -
Start by enqueuing the
root
node to initiate the traversal process. -
Begin a loop that continues until
current_queue
is empty. -
Now, determine the number of nodes at the current level by getting the length of
current_queue
. Also, initialize an empty list,level_nodes
, to store the data of nodes at this level. -
Next, iterate over the number of nodes in the current level:
- Dequeue a node (
temp
) from the front ofcurrent_queue
. - Append the string representation of the node’s data to
level_nodes
. - Enqueue the left and right child of the node(if any) we dequeued in the previous step into
current_queue
.
- Dequeue a node (
-
After processing all nodes at current levels, join the elements in
level_nodes
into a single string with “,” as a separator. -
Append this string to the
result
list, representing one level of the traversal. -
Once the traversal is complete, join all the level representations stored in the result list into a single string with " : " as the separator.
-
Print the resultant, the string which represents the level-order traversal of the binary tree.
Here’s an example to demonstrate the algorithm mentioned above:
Let’s look at the code for this solution below:
class Solution {public static String levelOrderTraversal(TreeNode<Integer> root) {List<String> result = new ArrayList<>();if (root == null) {return "None";}Queue<TreeNode<Integer>> current_queue = new ArrayDeque<TreeNode<Integer>>();current_queue.add(root);while (!current_queue.isEmpty()) {int levelSize = current_queue.size();List<String> levelNodes = new ArrayList<>();for (int i = 0; i < levelSize; i++) {TreeNode<Integer> temp = current_queue.poll();levelNodes.add(String.valueOf(temp.data));if (temp.left != null) {current_queue.add(temp.left);}if (temp.right != null) {current_queue.add(temp.right);}}result.add(String.join(", ", levelNodes));}return String.join(" : ", result);}public static void main(String[] argv) {List<TreeNode<Integer>> testCasesRoots = new ArrayList<>();List<TreeNode<Integer>> input1 = Arrays.asList(new TreeNode<>(100),new TreeNode<>(50),new TreeNode<>(200),new TreeNode<>(25),new TreeNode<>(75),new TreeNode<>(350));BinaryTree<Integer> tree1 = new BinaryTree<>(input1);testCasesRoots.add(tree1.root);List<TreeNode<Integer>> input2 = Arrays.asList(new TreeNode<>(25),new TreeNode<>(50),null,new TreeNode<>(100),new TreeNode<>(200),new TreeNode<>(350));BinaryTree<Integer> tree2 = new BinaryTree<>(input2);testCasesRoots.add(tree2.root);List<TreeNode<Integer>> input3 = Arrays.asList(new TreeNode<>(350),null,new TreeNode<>(100),null,new TreeNode<>(50),new TreeNode<>(25));BinaryTree<Integer> tree3 = new BinaryTree<>(input3);testCasesRoots.add(tree3.root);BinaryTree<Integer> tree4 = new BinaryTree<>(Arrays.asList(new TreeNode<>(100)));testCasesRoots.add(tree4.root);testCasesRoots.add(null);for (int i = 0; i < testCasesRoots.size(); i++) {if (i > 0) {System.out.println("\n");}System.out.println(i + 1 + ".\tBinary Tree");Print.displayTree(testCasesRoots.get(i));System.out.print("\n\tLevel order traversal: ");System.out.println(levelOrderTraversal(testCasesRoots.get(i)) + "\n");System.out.println(new String(new char[100]).replace('\0', '-'));}}}
Solution summary
To recap, the solution to this problem can be divided into the following steps:
- Start by initializing an empty result list and a queue, pushing the root onto the queue.
- While the queue is not empty, iterate over each level, dequeuing nodes one by one and enqueuing their children.
- Collect the data of nodes at each level, join them into strings, and append them to the result list.
- Finally, join the level strings with " : " and return the resulting string representing the level-order traversal.
Time complexity
The time complexity of this solution is linear, , where is the number of nodes, because every node is visited and printed only once.
Space complexity
The space complexity of this solution is linear, , since the algorithm instantiates queues that take up space of up to nodes. This is because the maximum queue size occurs at the level of the leaf nodes of a balanced binary tree.
Level up your interview prep. Join Educative to access 70+ hands-on prep courses.