Boundary Traversal of Binary Tree

Traverse the boundary of a binary tree in anti-clockwise direction.

Language Selection

Choose your preferred programming language

Showing: Python

Boundary Traversal of Binary Tree

Problem Statement

Given a binary tree, return the values of its boundary in anti-clockwise direction starting from root. Boundary includes left boundary, leaves, and right boundary in order without duplicate nodes.

The boundary of a binary tree is the concatenation of the root, the left boundary, the leaves, and the right boundary in order without duplicate nodes.

Examples

Example 1:

Input: root = [1,null,2,3,4]
Output: [1,3,4,2]

Explanation:
The boundary includes:
- Root: 1
- Left boundary: (empty)
- Leaves: 3, 4
- Right boundary: 2

Example 2:

Input: root = [1,2,3,4,5,6,null,null,null,7,8,9,10]
Output: [1,2,4,7,8,9,10,6,3]

Explanation:
The boundary includes:
- Root: 1
- Left boundary: 2, 4
- Leaves: 7, 8, 9, 10
- Right boundary: 6, 3

Constraints

  • 0 <= Number of nodes <= 10^4
  • -1000 <= Node.val <= 1000

Approach 1: Three-Step Boundary Traversal (Optimal)

Algorithm

Break down the boundary traversal into three parts:

  1. Left boundary (excluding leaves)
  2. Leaf nodes
  3. Right boundary (excluding leaves, in reverse order)

Steps:

  1. Add root to result
  2. Add left boundary nodes (top to bottom)
  3. Add leaf nodes (left to right)
  4. Add right boundary nodes (bottom to top)

Implementation

Python:

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

def boundaryOfBinaryTree(root):
    """
    Boundary traversal of binary tree
    Time: O(n)
    Space: O(h) where h is height of tree
    """
    if not root:
        return []
    
    result = []
    
    # Add root if it's not a leaf
    if not isLeaf(root):
        result.append(root.val)
    
    # Add left boundary
    addLeftBoundary(root.left, result)
    
    # Add leaves
    addLeaves(root, result)
    
    # Add right boundary
    addRightBoundary(root.right, result)
    
    return result

def isLeaf(node):
    """Check if node is a leaf"""
    return node and not node.left and not node.right

def addLeftBoundary(node, result):
    """Add left boundary nodes (excluding leaves)"""
    while node and not isLeaf(node):
        result.append(node.val)
        if node.left:
            node = node.left
        else:
            node = node.right

def addLeaves(node, result):
    """Add all leaf nodes"""
    if not node:
        return
    
    if isLeaf(node):
        result.append(node.val)
    else:
        addLeaves(node.left, result)
        addLeaves(node.right, result)

def addRightBoundary(node, result):
    """Add right boundary nodes (excluding leaves, in reverse order)"""
    stack = []
    while node and not isLeaf(node):
        stack.append(node.val)
        if node.right:
            node = node.right
        else:
            node = node.left
    
    # Add in reverse order
    while stack:
        result.append(stack.pop())

Java:

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public List<Integer> boundaryOfBinaryTree(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        
        if (root == null) {
            return result;
        }
        
        // Add root if it's not a leaf
        if (!isLeaf(root)) {
            result.add(root.val);
        }
        
        // Add left boundary
        addLeftBoundary(root.left, result);
        
        // Add leaves
        addLeaves(root, result);
        
        // Add right boundary
        addRightBoundary(root.right, result);
        
        return result;
    }
    
    private boolean isLeaf(TreeNode node) {
        return node != null && node.left == null && node.right == null;
    }
    
    private void addLeftBoundary(TreeNode node, List<Integer> result) {
        while (node != null && !isLeaf(node)) {
            result.add(node.val);
            if (node.left != null) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
    }
    
    private void addLeaves(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        
        if (isLeaf(node)) {
            result.add(node.val);
        } else {
            addLeaves(node.left, result);
            addLeaves(node.right, result);
        }
    }
    
    private void addRightBoundary(TreeNode node, List<Integer> result) {
        Stack<Integer> stack = new Stack<>();
        while (node != null && !isLeaf(node)) {
            stack.push(node.val);
            if (node.right != null) {
                node = node.right;
            } else {
                node = node.left;
            }
        }
        
        // Add in reverse order
        while (!stack.isEmpty()) {
            result.add(stack.pop());
        }
    }
}

Go:

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func boundaryOfBinaryTree(root *TreeNode) []int {
    var result []int
    
    if root == nil {
        return result
    }
    
    // Add root if it's not a leaf
    if !isLeaf(root) {
        result = append(result, root.Val)
    }
    
    // Add left boundary
    addLeftBoundary(root.Left, &result)
    
    // Add leaves
    addLeaves(root, &result)
    
    // Add right boundary
    addRightBoundary(root.Right, &result)
    
    return result
}

func isLeaf(node *TreeNode) bool {
    return node != nil && node.Left == nil && node.Right == nil
}

func addLeftBoundary(node *TreeNode, result *[]int) {
    for node != nil && !isLeaf(node) {
        *result = append(*result, node.Val)
        if node.Left != nil {
            node = node.Left
        } else {
            node = node.Right
        }
    }
}

func addLeaves(node *TreeNode, result *[]int) {
    if node == nil {
        return
    }
    
    if isLeaf(node) {
        *result = append(*result, node.Val)
    } else {
        addLeaves(node.Left, result)
        addLeaves(node.Right, result)
    }
}

func addRightBoundary(node *TreeNode, result *[]int) {
    var stack []int
    for node != nil && !isLeaf(node) {
        stack = append(stack, node.Val)
        if node.Right != nil {
            node = node.Right
        } else {
            node = node.Left
        }
    }
    
    // Add in reverse order
    for i := len(stack) - 1; i >= 0; i-- {
        *result = append(*result, stack[i])
    }
}

JavaScript:

class TreeNode {
    constructor(val, left, right) {
        this.val = (val === undefined ? 0 : val);
        this.left = (left === undefined ? null : left);
        this.right = (right === undefined ? null : right);
    }
}

function boundaryOfBinaryTree(root) {
    const result = [];
    
    if (!root) {
        return result;
    }
    
    // Add root if it's not a leaf
    if (!isLeaf(root)) {
        result.push(root.val);
    }
    
    // Add left boundary
    addLeftBoundary(root.left, result);
    
    // Add leaves
    addLeaves(root, result);
    
    // Add right boundary
    addRightBoundary(root.right, result);
    
    return result;
}

function isLeaf(node) {
    return node && !node.left && !node.right;
}

function addLeftBoundary(node, result) {
    while (node && !isLeaf(node)) {
        result.push(node.val);
        if (node.left) {
            node = node.left;
        } else {
            node = node.right;
        }
    }
}

function addLeaves(node, result) {
    if (!node) {
        return;
    }
    
    if (isLeaf(node)) {
        result.push(node.val);
    } else {
        addLeaves(node.left, result);
        addLeaves(node.right, result);
    }
}

function addRightBoundary(node, result) {
    const stack = [];
    while (node && !isLeaf(node)) {
        stack.push(node.val);
        if (node.right) {
            node = node.right;
        } else {
            node = node.left;
        }
    }
    
    // Add in reverse order
    while (stack.length > 0) {
        result.push(stack.pop());
    }
}

C#:

public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

public class Solution {
    public IList<int> BoundaryOfBinaryTree(TreeNode root) {
        List<int> result = new List<int>();
        
        if (root == null) {
            return result;
        }
        
        // Add root if it's not a leaf
        if (!IsLeaf(root)) {
            result.Add(root.val);
        }
        
        // Add left boundary
        AddLeftBoundary(root.left, result);
        
        // Add leaves
        AddLeaves(root, result);
        
        // Add right boundary
        AddRightBoundary(root.right, result);
        
        return result;
    }
    
    private bool IsLeaf(TreeNode node) {
        return node != null && node.left == null && node.right == null;
    }
    
    private void AddLeftBoundary(TreeNode node, List<int> result) {
        while (node != null && !IsLeaf(node)) {
            result.Add(node.val);
            if (node.left != null) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
    }
    
    private void AddLeaves(TreeNode node, List<int> result) {
        if (node == null) {
            return;
        }
        
        if (IsLeaf(node)) {
            result.Add(node.val);
        } else {
            AddLeaves(node.left, result);
            AddLeaves(node.right, result);
        }
    }
    
    private void AddRightBoundary(TreeNode node, List<int> result) {
        Stack<int> stack = new Stack<int>();
        while (node != null && !IsLeaf(node)) {
            stack.Push(node.val);
            if (node.right != null) {
                node = node.right;
            } else {
                node = node.left;
            }
        }
        
        // Add in reverse order
        while (stack.Count > 0) {
            result.Add(stack.Pop());
        }
    }
}

Complexity Analysis

  • Time Complexity: O(n) - Visit each node once
  • Space Complexity: O(h) - Recursion stack depth

Key Insights

  1. Three Components: Boundary = Root + Left Boundary + Leaves + Right Boundary
  2. Anti-clockwise Order: Left boundary (top-down), leaves (left-right), right boundary (bottom-up)
  3. No Duplicates: Avoid adding nodes multiple times
  4. Leaf Handling: Special handling for leaf nodes
  5. Edge Cases: Single node, skewed trees, empty trees

Edge Cases

  1. Empty Tree: Return empty list
  2. Single Node: Return [node.val]
  3. Left Skewed: Only left boundary and leaves
  4. Right Skewed: Only right boundary and leaves
  5. Complete Tree: All three components present

Follow-up Questions

  1. Clockwise Boundary: How would you traverse boundary in clockwise direction?
  2. Inner Boundary: Find the boundary of inner nodes (excluding leaves)?
  3. Kth Boundary: Find nodes at distance k from boundary?
  4. Boundary Sum: Calculate sum of all boundary nodes?
  5. Boundary Validation: Check if a tree has valid boundary structure?

Common Mistakes

  1. Duplicate Nodes: Adding same node multiple times
  2. Order Confusion: Wrong order for right boundary (should be bottom-up)
  3. Leaf Handling: Not properly identifying leaf nodes
  4. Root Handling: Adding root when it’s a leaf
  5. Edge Cases: Not handling single node or empty tree

Interview Tips

  1. Clarify Requirements: Ask about duplicate handling and order
  2. Discuss Approaches: Mention both three-step and single DFS approaches
  3. Handle Edge Cases: Discuss empty tree, single node scenarios
  4. Explain Algorithm: Walk through the three components clearly
  5. Consider Optimizations: Discuss space and time tradeoffs