Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

545. Boundary of Binary Tree - Leetcode Solution

Code Implementation

class Solution:
    def boundaryOfBinaryTree(self, root):
        if not root:
            return []
        
        res = []

        def isLeaf(node):
            return node and not node.left and not node.right

        def addLeftBoundary(node):
            while node:
                if not isLeaf(node):
                    res.append(node.val)
                node = node.left if node.left else node.right

        def addLeaves(node):
            if node:
                if isLeaf(node):
                    res.append(node.val)
                else:
                    addLeaves(node.left)
                    addLeaves(node.right)

        def addRightBoundary(node):
            tmp = []
            while node:
                if not isLeaf(node):
                    tmp.append(node.val)
                node = node.right if node.right else node.left
            res.extend(reversed(tmp))

        if not isLeaf(root):
            res.append(root.val)
        addLeftBoundary(root.left)
        addLeaves(root)
        addRightBoundary(root.right)
        return res
      
class Solution {
public:
    vector<int> boundaryOfBinaryTree(TreeNode* root) {
        vector<int> res;
        if (!root) return res;
        if (!isLeaf(root)) res.push_back(root->val);

        addLeftBoundary(root->left, res);
        addLeaves(root, res);
        addRightBoundary(root->right, res);
        return res;
    }
private:
    bool isLeaf(TreeNode* node) {
        return node && !node->left && !node->right;
    }
    void addLeftBoundary(TreeNode* node, vector<int>& res) {
        while (node) {
            if (!isLeaf(node)) res.push_back(node->val);
            node = node->left ? node->left : node->right;
        }
    }
    void addLeaves(TreeNode* node, vector<int>& res) {
        if (node) {
            if (isLeaf(node)) res.push_back(node->val);
            else {
                addLeaves(node->left, res);
                addLeaves(node->right, res);
            }
        }
    }
    void addRightBoundary(TreeNode* node, vector<int>& res) {
        vector<int> tmp;
        while (node) {
            if (!isLeaf(node)) tmp.push_back(node->val);
            node = node->right ? node->right : node->left;
        }
        for (int i = tmp.size() - 1; i >= 0; --i)
            res.push_back(tmp[i]);
    }
};
      
class Solution {
    public List<Integer> boundaryOfBinaryTree(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        if (!isLeaf(root)) res.add(root.val);

        addLeftBoundary(root.left, res);
        addLeaves(root, res);
        addRightBoundary(root.right, res);
        return res;
    }
    private boolean isLeaf(TreeNode node) {
        return node != null && node.left == null && node.right == null;
    }
    private void addLeftBoundary(TreeNode node, List<Integer> res) {
        while (node != null) {
            if (!isLeaf(node)) res.add(node.val);
            node = node.left != null ? node.left : node.right;
        }
    }
    private void addLeaves(TreeNode node, List<Integer> res) {
        if (node != null) {
            if (isLeaf(node)) res.add(node.val);
            else {
                addLeaves(node.left, res);
                addLeaves(node.right, res);
            }
        }
    }
    private void addRightBoundary(TreeNode node, List<Integer> res) {
        List<Integer> tmp = new ArrayList<>();
        while (node != null) {
            if (!isLeaf(node)) tmp.add(node.val);
            node = node.right != null ? node.right : node.left;
        }
        for (int i = tmp.size() - 1; i >= 0; --i)
            res.add(tmp.get(i));
    }
}
      
var boundaryOfBinaryTree = function(root) {
    if (!root) return [];
    const res = [];
    function isLeaf(node) {
        return node && !node.left && !node.right;
    }
    function addLeftBoundary(node) {
        while (node) {
            if (!isLeaf(node)) res.push(node.val);
            node = node.left ? node.left : node.right;
        }
    }
    function addLeaves(node) {
        if (node) {
            if (isLeaf(node)) res.push(node.val);
            else {
                addLeaves(node.left);
                addLeaves(node.right);
            }
        }
    }
    function addRightBoundary(node) {
        const tmp = [];
        while (node) {
            if (!isLeaf(node)) tmp.push(node.val);
            node = node.right ? node.right : node.left;
        }
        for (let i = tmp.length - 1; i >= 0; --i)
            res.push(tmp[i]);
    }
    if (!isLeaf(root)) res.push(root.val);
    addLeftBoundary(root.left);
    addLeaves(root);
    addRightBoundary(root.right);
    return res;
};
      

Problem Description

Given the root of a binary tree, return the values of its boundary in anti-clockwise direction starting from the root. The boundary includes:
  • The left boundary: the path from the root to the left-most node, excluding leaves.
  • All the leaves, from left to right.
  • The right boundary: the path from the root to the right-most node, excluding leaves, in bottom-up order.

Each node value should appear only once in the boundary. Do not duplicate leaves or root.

The input is a binary tree represented by the root node. The output is a list of integers representing the boundary in the specified order.

Thought Process

At first glance, the problem seems to ask for a traversal, but not a standard preorder, inorder, or postorder. Instead, we need to carefully walk along the "edge" of the tree, collecting nodes in a particular order and avoiding duplicates.

  • The left boundary is the path from the root down to the leftmost node, but we must not include leaf nodes (they will be added later as leaves).
  • The leaves are all nodes without children, gathered from left to right.
  • The right boundary is the path from the root down to the rightmost node, again skipping leaves, and we need to add these in reverse order (bottom-up).

A brute-force approach might try to traverse the tree multiple times or use extra data structures to track duplicates, but this can get messy. Instead, it's better to break the problem into three clear steps: collect the left boundary, collect the leaves, and collect the right boundary (in reverse), ensuring no node is added twice.

Solution Approach

  • Step 1: Add the root value. If the root is not a leaf node, add its value as the starting point of the boundary.
  • Step 2: Traverse the left boundary. Starting from root.left, traverse downward. At each step, add the node's value if it's not a leaf. Prefer left children; if none, go right. Stop when you reach a leaf or null.
  • Step 3: Add all leaves. Traverse the tree (using preorder or inorder). For each node, if it's a leaf, add its value. This ensures we get leaves from left to right.
  • Step 4: Traverse the right boundary. Starting from root.right, traverse downward. At each step, add the node's value to a temporary list if it's not a leaf (to avoid duplication). Prefer right children; if none, go left. After traversal, reverse this temporary list and append it to the result, ensuring bottom-up order.
  • Edge Cases: If the tree is a single node, or if left/right boundaries are missing, the code handles these by checking for nulls and leaf status.

We use helper functions for each part (left boundary, leaves, right boundary) to keep the code modular and readable. This approach is both efficient and easy to understand.

Example Walkthrough

Consider the following binary tree:

        1
       / \
      2   3
     / \   \
    4   5   6
       / \
      7   8
  
  • Root: Add 1 to the result.
  • Left boundary: Traverse from 2 down. Add 2 (not a leaf). Next, go to 4 (a leaf), so stop.
  • Leaves: From left to right: 4, 7, 8, 6.
  • Right boundary: Traverse from 3 down. 3 is not a leaf, add to temp list. Go to 6 (a leaf), so stop. Reverse the temp list ([3]) and add to result.

Final boundary order: [1, 2, 4, 7, 8, 6, 3]

Time and Space Complexity

  • Brute-force Approach: If we used extra data structures to track duplicates and traversed the tree multiple times, the time complexity would still be O(N), but space and logic would be less efficient.
  • Optimized Approach: Each node is visited at most once (in one of the left boundary, leaves, or right boundary traversals), so the time complexity is O(N), where N is the number of nodes in the tree.
  • Space Complexity: O(N) for the result list. The recursion stack may be O(H), where H is the height of the tree.

Summary

By breaking the problem into three distinct traversals—left boundary, leaves, and right boundary—we can efficiently collect the boundary of a binary tree in anti-clockwise order without duplicating nodes. The solution uses clear helper functions to keep logic modular and readable, and handles edge cases naturally. This approach is both optimal and elegant, making the problem approachable for programmers at any level.