Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Tree Traversal - Understanding preorder, inorder, and postorder traversal patterns
  • Recursion - Using recursive functions to traverse tree structures and collect nodes
  • Stack Data Structure - Using a stack to reverse the order of elements (for the right boundary)

1. Simple Solution

Intuition

The boundary of a binary tree consists of three parts traversed in order: the left boundary (top to bottom, excluding leaves), all leaf nodes (left to right), and the right boundary (bottom to top, excluding leaves). We handle each part separately: traverse down the left edge, collect all leaves via recursion, and traverse down the right edge while using a stack to reverse the order.

Algorithm

  1. If the root is null, return an empty list.
  2. Add the root to the result if it is not a leaf.
  3. Traverse the left boundary:
    • Start from root.left and keep moving left (or right if left is null).
    • Add each non-leaf node to the result.
  4. Collect all leaf nodes using a recursive helper that adds leaves left to right.
  5. Traverse the right boundary:
    • Start from root.right and keep moving right (or left if right is null).
    • Push each non-leaf node onto a stack.
    • Pop all values from the stack and add them to the result (reversing the order).
  6. Return the result.
class Solution:
    def isLeaf(self, t: Optional[TreeNode]) -> bool:
        return t.left is None and t.right is None
    
    def addLeaves(self, res: List[int], root: Optional[TreeNode]) -> None:
        if self.isLeaf(root):
            res.append(root.val)
        else:
            if root.left is not None:
                self.addLeaves(res, root.left)
            if root.right is not None:
                self.addLeaves(res, root.right)
    
    def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if root is None:
            return res
        
        if not self.isLeaf(root):
            res.append(root.val)
        
        t = root.left
        while t is not None:
            if not self.isLeaf(t):
                res.append(t.val)
            if t.left is not None:
                t = t.left
            else:
                t = t.right
        
        self.addLeaves(res, root)
        
        stack = []
        t = root.right
        while t is not None:
            if not self.isLeaf(t):
                stack.append(t.val)
            if t.right is not None:
                t = t.right
            else:
                t = t.left
        
        while stack:
            res.append(stack.pop())
        
        return res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn is the number of nodes in the tree


2. Using PreOrder Traversal

Intuition

We can collect all boundary nodes in a single preorder traversal by tracking where each node belongs. Using a flag system, we mark nodes as: root (0), left boundary (1), right boundary (2), or internal (3). During traversal, we determine each child's flag based on the parent's flag and whether siblings exist. Left boundary nodes go directly to the result, right boundary nodes are collected in reverse order, and leaves are gathered separately.

Algorithm

  1. Create three lists: left_boundary, right_boundary, and leaves.
  2. Perform a preorder traversal with a flag indicating the node's role:
    • If the node is on the right boundary (flag = 2), prepend its value to right_boundary.
    • If the node is on the left boundary or is the root (flag = 0 or 1), append its value to left_boundary.
    • If the node is a leaf and not already counted, append to leaves.
  3. For each child, calculate its flag:
    • Left child inherits left boundary status, or becomes right boundary if it is the only child of a right boundary node.
    • Right child inherits right boundary status, or becomes left boundary if it is the only child of a left boundary node.
  4. Concatenate left_boundary + leaves + right_boundary and return.
class Solution:
    def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> List[int]:
        left_boundary, right_boundary, leaves = [], [], []
        self.preorder(root, left_boundary, right_boundary, leaves, 0)
        left_boundary.extend(leaves)
        left_boundary.extend(right_boundary)
        return left_boundary
    
    def is_leaf(self, cur):
        return cur.left is None and cur.right is None
    
    def is_right_boundary(self, flag):
        return flag == 2
    
    def is_left_boundary(self, flag):
        return flag == 1
    
    def is_root(self, flag):
        return flag == 0
    
    def left_child_flag(self, cur, flag):
        if self.is_left_boundary(flag) or self.is_root(flag):
            return 1
        elif self.is_right_boundary(flag) and cur.right is None:
            return 2
        else:
            return 3
    
    def right_child_flag(self, cur, flag):
        if self.is_right_boundary(flag) or self.is_root(flag):
            return 2
        elif self.is_left_boundary(flag) and cur.left is None:
            return 1
        else:
            return 3
    
    def preorder(self, cur, left_boundary, right_boundary, leaves, flag):
        if cur is None:
            return
        
        if self.is_right_boundary(flag):
            right_boundary.insert(0, cur.val)
        elif self.is_left_boundary(flag) or self.is_root(flag):
            left_boundary.append(cur.val)
        elif self.is_leaf(cur):
            leaves.append(cur.val)
            
        self.preorder(cur.left, left_boundary, right_boundary, leaves, self.left_child_flag(cur, flag))
        self.preorder(cur.right, left_boundary, right_boundary, leaves, self.right_child_flag(cur, flag))

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn is the number of nodes in the tree


Common Pitfalls

Including Leaves in the Left or Right Boundary

Leaf nodes should only appear once in the leaves section, not in the left or right boundary. Adding a leaf while traversing the left boundary causes duplicates in the result.

# Wrong: not checking for leaf before adding to boundary
while t is not None:
    res.append(t.val)  # Should check: if not isLeaf(t)
    t = t.left if t.left else t.right

Incorrect Right Boundary Order

The right boundary must be added in bottom-to-top order. A common mistake is adding nodes directly to the result during traversal (top-to-bottom), instead of using a stack or reversing at the end.

Missing the Single-Child Case in Boundary Traversal

When traversing the left boundary, if a node has no left child, you must continue with the right child (and vice versa for the right boundary). Stopping traversal at missing children misses boundary nodes deeper in the tree.