title: Binary Tree Level Order Traversal II
slug: binary-tree-level-order-traversal-ii
difficulty: medium
leetcode_id: 107
leetcode_url: https://leetcode.com/problems/binary-tree-level-order-traversal-ii/
categories:
  - trees
  - queue
patterns:
  - bfs
  - tree-traversal

function_signature: "def level_order_bottom(root: TreeNode) -> list[list[int]]:"

test_cases:
  visible:
    - input: { root: [3, 9, 20, null, null, 15, 7] }
      expected: [[15, 7], [9, 20], [3]]
    - input: { root: [1] }
      expected: [[1]]
    - input: { root: [] }
      expected: []
  hidden:
    - input: { root: [1, 2, 3, 4, 5, 6, 7] }
      expected: [[4, 5, 6, 7], [2, 3], [1]]
    - input: { root: [1, 2, null, 3, null, 4] }
      expected: [[4], [3], [2], [1]]
    - input: { root: [1, null, 2, null, 3] }
      expected: [[3], [2], [1]]
    - input: { root: [-10, 9, 20, null, null, 15, 7] }
      expected: [[15, 7], [9, 20], [-10]]

description: |
  Given the `root` of a binary tree, return *the bottom-up level order traversal of its nodes' values* (i.e., from left to right, level by level from leaf to root).

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

examples:
  - input: "root = [3,9,20,null,null,15,7]"
    output: "[[15,7],[9,20],[3]]"
    explanation: "The deepest level (15, 7) comes first, then level 1 (9, 20), and finally the root level (3)."
  - input: "root = [1]"
    output: "[[1]]"
    explanation: "Single node forms its own level."
  - input: "root = []"
    output: "[]"
    explanation: "Empty tree returns an empty list."

explanation:
  intuition: |
    This problem is a twist on the classic level order traversal. Instead of reading the tree from top to bottom, we want to read it from bottom to top — like viewing a family photo starting with the grandchildren and working up to the grandparents.

    Think of it like this: if we already know how to do a standard level order traversal (BFS), we get levels in the order `[root, level1, level2, ...]`. To get the bottom-up order, we simply **reverse the result** at the end: `[level2, level1, root]`.

    The key insight is that **we don't need a fundamentally different algorithm**. BFS naturally processes the tree level by level from top to bottom. Rather than trying to traverse bottom-up (which is awkward), we traverse top-down and then reverse. This is both simpler and equally efficient.

    Alternatively, we can build the result in reverse order by inserting each level at the front of the result list, but this is less efficient in most languages.

  approach: |
    We solve this using **BFS with Final Reversal**:

    **Step 1: Handle the empty tree case**

    - If `root` is `None`, return an empty list immediately

    &nbsp;

    **Step 2: Initialise the queue and result**

    - Create a queue starting with just the root node
    - Create an empty result list to hold each level's values

    &nbsp;

    **Step 3: Process level by level (standard BFS)**

    - While the queue is not empty:
      - Capture `level_size = len(queue)` — this is how many nodes are in the current level
      - Create a `level_values` list for this level
      - Process exactly `level_size` nodes:
        - Dequeue a node, add its value to `level_values`
        - Enqueue its left child (if exists)
        - Enqueue its right child (if exists)
      - Append `level_values` to the result

    &nbsp;

    **Step 4: Reverse and return**

    - Reverse the result list so the deepest level comes first
    - Return the reversed result

    &nbsp;

    The reversal is O(n) and doesn't change the overall complexity. This approach is clean because it reuses the well-known BFS level order pattern.

  common_pitfalls:
    - title: Over-Engineering the Traversal
      description: |
        A common mistake is trying to traverse the tree from bottom to top directly. This is unnecessarily complex — you'd need to first find the depth of the tree, then process nodes by depth in reverse order.

        The simpler approach: do standard BFS (which is well-understood and easy to implement), then reverse the result at the end.
      wrong_approach: "Trying to traverse bottom-up directly"
      correct_approach: "Standard BFS, then reverse the result"

    - title: Inserting at Front Instead of Reversing
      description: |
        Some solutions insert each level at index 0 of the result list: `result.insert(0, level_values)`. While this produces the correct output, `insert(0, ...)` on a Python list is O(n) for each insertion.

        With k levels, this leads to O(n × k) operations. Appending and reversing at the end is O(n) total.
      wrong_approach: "result.insert(0, level_values) for each level"
      correct_approach: "result.append(level_values), then result.reverse() at the end"

    - title: Using List as Queue
      description: |
        In Python, `list.pop(0)` is O(n) because all remaining elements must shift. For a tree with n nodes, this makes the algorithm O(n²) instead of O(n).

        Use `collections.deque` which has O(1) `popleft()`.
      wrong_approach: "queue = []; queue.pop(0)"
      correct_approach: "queue = deque(); queue.popleft()"

    - title: Forgetting the Empty Tree Check
      description: |
        If `root` is `None`, the queue starts empty, and the while loop never executes — but some implementations might crash trying to access root.

        Always check `if not root: return []` at the start.
      wrong_approach: "Assuming root is never None"
      correct_approach: "if not root: return []"

  key_takeaways:
    - "**Transform, don't reinvent**: When a problem is a variation of a classic pattern, solve the classic version and transform the output"
    - "**BFS = level order**: Breadth-first search naturally processes trees level by level — master this fundamental pattern"
    - "**Reversal is cheap**: Reversing a list is O(n), same as the traversal — don't avoid it to pursue a 'clever' but slower solution"
    - "**Same pattern, many problems**: This technique extends to zigzag traversal, right side view, level averages, and more"

  time_complexity: "O(n). Every node is visited exactly once, and the final reversal is also O(n)."
  space_complexity: "O(n). The queue holds at most one level of nodes (~n/2 in a complete binary tree), and the result stores all n node values."

solutions:
  - approach_name: BFS with Reversal
    is_optimal: true
    code: |
      from collections import deque

      class TreeNode:
          def __init__(self, val=0, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right

      def level_order_bottom(root: TreeNode | None) -> list[list[int]]:
          # Handle empty tree
          if not root:
              return []

          result = []
          queue = deque([root])

          while queue:
              # Capture current level size BEFORE processing
              level_size = len(queue)
              level_values = []

              # Process exactly level_size nodes (all nodes at current level)
              for _ in range(level_size):
                  node = queue.popleft()
                  level_values.append(node.val)

                  # Add children for next level
                  if node.left:
                      queue.append(node.left)
                  if node.right:
                      queue.append(node.right)

              # This level is complete
              result.append(level_values)

          # Reverse to get bottom-up order
          result.reverse()
          return result
    explanation: |
      **Time Complexity:** O(n) — Each node added and removed from queue exactly once, plus O(n) for the reversal.

      **Space Complexity:** O(n) — Queue can hold up to n/2 nodes, and result stores all n values.

      Standard BFS processes levels top-to-bottom. We collect each level, then reverse the result at the end. The reversal adds O(n) time but doesn't change the overall complexity.

  - approach_name: DFS with Level Tracking
    is_optimal: false
    code: |
      def level_order_bottom(root: TreeNode | None) -> list[list[int]]:
          result = []

          def dfs(node: TreeNode | None, level: int) -> None:
              if not node:
                  return

              # First time at this level? Create new list
              if level == len(result):
                  result.append([])

              # Add current node to its level
              result[level].append(node.val)

              # Recurse to children (level + 1)
              dfs(node.left, level + 1)
              dfs(node.right, level + 1)

          dfs(root, 0)
          # Reverse to get bottom-up order
          result.reverse()
          return result
    explanation: |
      **Time Complexity:** O(n) — Visit each node once, plus O(n) for reversal.

      **Space Complexity:** O(h) — Recursion stack depth equals tree height (O(log n) for balanced, O(n) for skewed).

      DFS alternative: pass the level as a parameter. Each node appends to its level's list. Left-to-right order is preserved because we recurse left before right. Finally, reverse the result for bottom-up order.
