Binary Tree Level Order Traversal II

Updated on 12 May, 2025
Binary Tree Level Order Traversal II header image

Problem Statement

In this task, we are dealing with a binary tree. The goal is to perform a traversal that records the values of nodes level by level, starting from the leaf nodes and moving up to the root. This order of traversal is known as a bottom-up level order traversal. Each level's node values should be listed from left to right, and the levels themselves should be listed from bottom (leaf) to top (root). The challenge involves both understanding the structure of the given binary tree and implementing an algorithm to traverse and record values in the required order.

Examples

Example 1

Input:

root = [3,9,20,null,null,15,7]

Output:

[[15,7],[9,20],[3]]

Example 2

Input:

root = [1]

Output:

[[1]]

Example 3

Input:

root = []

Output:

[]

Constraints

  • The number of nodes in the tree is in the range [0, 2000].
  • -1000 <= Node.val <= 1000

Approach and Intuition

To tackle this problem, the ideal way is to use Breadth-First Search (BFS) due to its nature of exploring nodes level by level. However, since we need a bottom-up level order, we will modify our approach to reverse the typical top-down level order result.

  1. First, check if the root is null. If so, return an empty list as there are no nodes to traverse.
  2. Initialize a queue to help with BFS traversal. This queue will store nodes along with their corresponding tree level.
  3. Use a list (or deque for efficiency) to hold the final results and a temporary list for nodes at the current level.
  4. As you dequeue nodes, enqueue their child nodes (left first, then right).
  5. Once an entire level is processed, append the list of node values for that level to the beginning of your result list. This will ensure that the bottom-most level ends up at the end of the result list, effectively reversing the order without additional operations.
  6. Continue the BFS until you've processed all levels of the tree.
  7. Return the list containing the reversed levels.

Some key points to note:

  • The input tree may have 0 nodes, in which case we immediately return an empty list.
  • Node values can range from -1000 to 1000, which doesn't affect the traversal or recording mechanism but is essential to ensure the traversal algorithm handles the range correctly.
  • The tree can have a maximum of 2000 nodes, meaning the BFS will perform efficiently within this limit.

Solutions

  • C++
  • Java
  • C
  • JavaScript
  • Python
cpp
class Solution {
public:
    vector<vector<int>> bottomUpLevelOrder(TreeNode* root) {
        vector<vector<int>> result;
        if (!root) return result;
        deque<TreeNode*> queue_next;
        queue_next.push_back(root);
        while (!queue_next.empty()) {
            deque<TreeNode*> queue_current = queue_next;
            queue_next.clear();
            result.push_back(vector<int>());
            for (TreeNode* node : queue_current) {
                result.back().push_back(node->val);
                if (node->left) queue_next.push_back(node->left);
                if (node->right) queue_next.push_back(node->right);
            }
        }
        reverse(result.begin(), result.end());
        return result;
    }
};

The given C++ code implements a solution for the problem of performing a bottom-up level order traversal on a binary tree. The function bottomUpLevelOrder provides a structured way to traverse the tree starting from the root node towards the leaves, and then collecting the values level by level from bottom to top.

  • The TreeNode structure is referenced, and it is assumed that each node has val, left, and right attributes.
  • A vector of vector<int>> named result is used to store the integers at each level of the tree.
  • A deque (double-ended queue) named queue_next helps in maintaining the current level's nodes while traversing the tree using breadth-first search (BFS).
  • The function starts by checking if the root is nullptr, immediately returning an empty result if true.
  • If the root is not nullptr, the root node is added to queue_next.
  • A while loop is used to process each tree level until queue_next is empty.
    • A queue_current deque copies all nodes from queue_next and queue_next is then cleared.
    • For every node in queue_current:
      • The node's value is appended to the current last vector in result.
      • If the node has a left child, it is added to queue_next.
      • If the node has a right child, it is also added to queue_next.
  • After processing all levels in direct top-to-bottom order, the vectors inside result are reversed to meet the bottom-up requirement using the reverse function, thus rearranging them from bottom level to top level.
  • Finally, result is returned, now correctly representing the level order traversal from the bottom-up.

This implementation ensures that the tree values are collected and organized efficiently while respecting the bottom-up traversal constraint.

java
class Solution {
    public List<List<Integer>> bottomUpLevelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;

        ArrayDeque<TreeNode> queueNext = new ArrayDeque() {{ offer(root); }};
        ArrayDeque<TreeNode> queueCurrent = new ArrayDeque();

        while (!queueNext.isEmpty()) {
            queueCurrent = queueNext.clone();
            queueNext.clear();
            result.add(new ArrayList<Integer>());

            for (TreeNode node : queueCurrent) {
                result.get(result.size() - 1).add(node.val);
                if (node.left != null) queueNext.offer(node.left);
                if (node.right != null) queueNext.offer(node.right);
            }
        }

        Collections.reverse(result);
        return result;
    }
}

Implement bottom-up level order traversal of a binary tree in Java, retrieving values from the tree's nodes in reverse level order—starting from the bottommost nodes to the root. Follow these steps:

  1. Begin by initializing a result list (result) to store the values of each level.
  2. Check if the tree root is null. If true, immediately return the empty result list, since an empty tree has no levels to traverse.
  3. Use two ArrayDeque<TreeNode> instances, queueNext to track nodes being accessed in the next level and queueCurrent to hold nodes being accessed in the current level. Start queueNext with the tree root.
  4. While queueNext is not empty:
    • Clone queueNext to queueCurrent and clear queueNext.
    • Add a new list to result to store the current level nodes’ values.
    • Traverse each node in queueCurrent:
      • Add the node's value to the last list in result.
      • If the node has a left child, add it to queueNext.
      • If the node has a right child, add it to queueNext.
  5. After processing all levels, reverse the result list so that the bottom-most level appears first.

This implementation captures all levels in the tree and then reverses the list to ensure the traversal is from bottom to top. Utilize ArrayDeque for efficient FIFO queue management and the dynamic resizing capability of ArrayList to handle varying numbers of nodes at each tree level.

c
struct Element {
    struct TreeNode* data;
    struct Element* nextElement;
};

struct DataQueue {
    struct Element *frontElement, *backElement;
};

struct Element* createNewElement(struct TreeNode* treeNode) {
    struct Element* newElement = (struct Element*)malloc(sizeof(struct Element));
    newElement->data = treeNode;
    newElement->nextElement = NULL;
    return newElement;
};

struct DataQueue* initializeQueue() {
    struct DataQueue* newQueue = (struct DataQueue*)malloc(sizeof(struct DataQueue));
    newQueue->frontElement = newQueue->backElement = NULL;
    return newQueue;
};

void enqueue(struct DataQueue* queue, struct TreeNode* treeNode) {
    struct Element* element = createNewElement(treeNode);
    if (queue->backElement == NULL) {
        queue->frontElement = queue->backElement = element;
        return;
    }
    queue->backElement->nextElement = element;
    queue->backElement = element;
}

void dequeue(struct DataQueue* queue) {
    if (queue->frontElement == NULL) return;
    struct Element* tempElement = queue->frontElement;
    queue->frontElement = queue->frontElement->nextElement;
    if (queue->frontElement == NULL) queue->backElement = NULL;
    free(tempElement);
}

int** reversedLevelOrderTraversal(struct TreeNode* rootNode, int* returnSize, int** returnColumnSizes) {
    int** levelsArray = malloc(sizeof(int*) * 10000);
    *returnColumnSizes = malloc(sizeof(int) * 10000);
    *returnSize = 0;
    if (rootNode == NULL) return levelsArray;
    struct DataQueue* rootQueue = initializeQueue();
    enqueue(rootQueue, rootNode);
    while (rootQueue->frontElement != NULL) {
        struct DataQueue* tempQueue = initializeQueue();
        levelsArray[*returnSize] = malloc(sizeof(int) * 2000);
        (*returnColumnSizes)[*returnSize] = 0;
        while (rootQueue->frontElement != NULL) {
            struct TreeNode* currentNode = rootQueue->frontElement->data;
            dequeue(rootQueue);
            levelsArray[*returnSize][(*returnColumnSizes)[*returnSize]] = currentNode->val;
            (*returnColumnSizes)[*returnSize]++;
            if (currentNode->left) enqueue(tempQueue, currentNode->left);
            if (currentNode->right) enqueue(tempQueue, currentNode->right);
        }
        *returnSize += 1;
        rootQueue = tempQueue;
    }
    int start = 0, end = *returnSize - 1;
    while (start < end) {
        int tempCount = (*returnColumnSizes)[start];
        int* tempLevel = levelsArray[start];
        (*returnColumnSizes)[start] = (*returnColumnSizes)[end];
        levelsArray[start] = levelsArray[end];
        (*returnColumnSizes)[end] = tempCount;
        levelsArray[end] = tempLevel;
        start++;
        end--;
    }
    return levelsArray;
}

In the provided solution for the problem of performing a reverse level order traversal on a binary tree, you implement several critical components in C:

  • Data Structures:

    • struct Element: Represents an element in a queue, containing a pointer to a TreeNode and a pointer to the next element.
    • struct DataQueue: Utilized to manage the queue operations with pointers to the front and back elements.
  • Queue Operations:

    • initializeQueue(): Initializes an empty queue.
    • enqueue(): Adds a tree node to the end of the queue.
    • dequeue(): Removes a tree node from the front of the queue.
    • createNewElement(): Helper function to create a new queue element.
  • Traversal Logic:

    • reversedLevelOrderTraversal(): Orchestrates the reverse level order traversal:
      1. Initialize necessary structures and variables.
      2. Use a while loop to process each level of the tree.
      3. For each node at the current level, its children are added to a temporary queue.
      4. After processing all levels in normal order, the levels are reversed to achieve the desired reverse order.

The function handles dynamic memory allocation for the levels array and ensures proper memory management through the use of malloc and free. It also accommodates nodes at each level and appropriately adjusts the size of supporting arrays.

This approach ensures that you can perform the level order traversal and then simply reverse the results, thereby efficiently solving the problem using queue data structures standard in breadth-first traversal of trees.

js
var reverseLevelOrder = function (node) {
    let result = [];
    if (node == null) return result;
    let queue = [node];
    while (queue.length > 0) {
        let levelNodes = [...queue];
        queue = [];
        result.unshift([]);
        for (let item of levelNodes) {
            result[0].push(item.val);
            if (item.left != null) queue.push(item.left);
            if (item.right != null) queue.push(item.right);
        }
    }
    return result;
};

In this walkthrough, focus on implementing a function to perform a reverse level order traversal of a binary tree. This involves visiting all nodes of the tree in a bottom-up manner.

  • Understand the main function reverseLevelOrder, which takes a parameter node, referring to the root of the binary tree.
  • Declare an array result that will store the final reverse level order traversal output levels.
  • Start by checking if the input node is null. If it is, return an empty list immediately.
  • Utilize a queue to facilitate level order traversal by initiating it with the root node [node].
  • Use a while loop to process elements until the queue is empty, which implies all levels have been processed:
    1. Copy queue to levelNodes to manage nodes at the current level.
    2. Reset queue for the upcoming level.
    3. Introduce a new sublist at the beginning of result with unshift, allowing inverted stacking of node values.
    4. Iterate over all nodes in levelNodes:
      1. Append the current node's value to the first sublist in result.
      2. Add the left child to the queue if it exists.
      3. Add the right child to the queue if it exists.
  • End the while loop once all nodes are processed.
  • Return the result which now contains all tree levels in reverse order.

Remember, this approach guarantees that each level of nodes is visited in a traditional level order but stored in reverse, achieving the desired bottom-up level order traversal.

python
class Solution:
    def reverseLevelOrder(self, root: TreeNode) -> List[List[int]]:
        results = []
        queue = deque([root])

        while root and queue:
            current = queue
            queue = deque()
            results.append([])

            for node in current:
                results[-1].append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)

        return results[::-1]

This Python code aims to solve the problem of performing a reverse level order traversal on a binary tree. The result should display the node values from the bottom to the top level, left to right within each level.

Here's a breakdown of the code process:

  • Initialize an empty list results to hold the values of the tree nodes.
  • Use a deque (double-ended queue) to facilitate efficient popping from the front and appending to the back while traversing the binary tree.
  • Start with the root node in the queue.
  • Use a while loop to continue processing as long as there are nodes in the queue.
  • Inside the loop, utilize a for loop to iterate through nodes at the current level. Extract node values, and append them to the result list for that level.
  • Manage node children by appending left and right children to the queue if available.
  • After traversing all levels in normal order, reverse the results list to achieve the bottom-up level order traversal.

Ensure you have a TreeNode class defined, which the function expects to receive as input. This function adjusts the tree node handling dynamically, storing results as lists of lists, with each sublist representing a level of the tree. The final returned list is reversed to match the bottom-up requirement.

Comments

No comments yet.