A clear explanation of collecting the boundary of a binary tree using separate left boundary, leaves, and right boundary traversals.
Problem Restatement
We are given the root of a binary tree.
We need to return the boundary of the tree in anti-clockwise order starting from the root.
The boundary consists of three parts:
- Left boundary
- Leaf nodes
- Right boundary in reverse order
There are important rules:
| Part | Rule |
|---|---|
| Root | Included once |
| Left boundary | Exclude leaves |
| Leaves | Include all leaves from left to right |
| Right boundary | Exclude leaves and reverse order |
| Duplicates | No node should appear twice |
The left boundary is formed by always preferring the left child when moving downward. If no left child exists, move to the right child.
The right boundary similarly prefers the right child first.
The official problem defines the boundary as root, left boundary, leaves, and reversed right boundary, without duplicates. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | Root of a binary tree |
| Output | Boundary node values in anti-clockwise order |
| Leaf rule | Include all leaves exactly once |
| Boundary rule | Exclude leaves from left/right boundaries |
Function shape:
def boundaryOfBinaryTree(root: Optional[TreeNode]) -> list[int]:
...Examples
Consider:
1
\
2
/ \
3 4The boundary is:
1 -> 3 -> 4 -> 2Explanation:
| Part | Nodes |
|---|---|
| Root | 1 |
| Left boundary | none |
| Leaves | 3, 4 |
| Right boundary reversed | 2 |
Output:
[1, 3, 4, 2]Another example:
1
/ \
2 3
/ \ \
4 5 6
/ \ /
7 8 9Boundary traversal:
| Part | Nodes |
|---|---|
| Root | 1 |
| Left boundary | 2 |
| Leaves | 4, 7, 8, 9 |
| Right boundary reversed | 6, 3 |
Final order:
[1, 2, 4, 7, 8, 9, 6, 3]First Thought: One DFS Around the Tree
A natural idea is to walk around the outside of the tree.
But handling all cases in one traversal becomes complicated:
- Leaves may appear on both sides.
- Left and right boundaries must exclude leaves.
- Right boundary must be reversed.
- Nodes must not repeat.
A cleaner solution is to treat each part independently.
Key Insight
The boundary can be built in exactly this order:
root
+
left boundary
+
all leaves
+
reversed right boundaryEach part has a simple rule.
So instead of trying to solve everything in one DFS, we write three separate traversals:
- Add left boundary
- Add leaves
- Add right boundary
This keeps the logic easy to verify.
Algorithm
- If the tree is empty, return
[]. - Add the root value.
- Traverse the left boundary:
- Prefer left child.
- Otherwise use right child.
- Skip leaves.
- Traverse all leaves:
- DFS over the whole tree.
- Add only leaf nodes.
- Traverse the right boundary:
- Prefer right child.
- Otherwise use left child.
- Skip leaves.
- Add nodes in reverse order.
- Return the result.
Correctness
The algorithm adds nodes in exactly the required anti-clockwise boundary order.
The root is added first.
The left boundary traversal starts from the root’s left child and always moves downward along the outer edge of the tree. Leaf nodes are excluded so they are not duplicated later during leaf collection.
The leaf traversal visits every node in the tree and adds exactly those nodes with no children. Since every leaf is visited once and boundary traversals skip leaves, each leaf appears exactly once in the final result.
The right boundary traversal follows the outer right edge of the tree, excluding leaves. Nodes are collected during downward traversal and then appended in reverse order, which produces the required bottom-up right boundary order.
Every boundary node belongs to exactly one of these categories:
- root
- left boundary
- leaf
- right boundary
and the traversal rules prevent duplicates between categories.
Therefore, the algorithm returns the correct tree boundary in anti-clockwise order.
Complexity
Let n be the number of nodes.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Every node is visited at most a constant number of times |
| Space | O(h) | Recursion stack height |
h is the tree height.
In a balanced tree:
h = O(log n)In a skewed tree:
h = O(n)Implementation
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from typing import Optional
class Solution:
def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> list[int]:
if root is None:
return []
def is_leaf(node: TreeNode) -> bool:
return node.left is None and node.right is None
boundary = [root.val]
def add_left_boundary(node: Optional[TreeNode]) -> None:
while node:
if not is_leaf(node):
boundary.append(node.val)
if node.left:
node = node.left
else:
node = node.right
def add_leaves(node: Optional[TreeNode]) -> None:
if node is None:
return
if is_leaf(node):
boundary.append(node.val)
return
add_leaves(node.left)
add_leaves(node.right)
def add_right_boundary(node: Optional[TreeNode]) -> None:
stack = []
while node:
if not is_leaf(node):
stack.append(node.val)
if node.right:
node = node.right
else:
node = node.left
while stack:
boundary.append(stack.pop())
add_left_boundary(root.left)
if not is_leaf(root):
add_leaves(root)
add_right_boundary(root.right)
return boundaryCode Explanation
We first handle the empty tree:
if root is None:
return []The helper:
is_leaf(node)checks whether a node has no children.
The root is always included first:
boundary = [root.val]Left Boundary
The left boundary traversal moves downward:
if node.left:
node = node.left
else:
node = node.rightThis follows the outermost edge.
Leaves are skipped:
if not is_leaf(node):because leaves are handled separately.
Leaves
The leaf traversal performs DFS over the entire tree.
If a node is a leaf:
if is_leaf(node):
boundary.append(node.val)Otherwise we continue recursively.
Right Boundary
The right boundary traversal is similar to the left boundary, but it prefers the right child first.
The important difference is that right-boundary nodes must appear bottom-up.
So we first collect them into a stack:
stack.append(node.val)Then reverse the order by popping:
while stack:
boundary.append(stack.pop())Edge Cases
Single Node
1The root is also a leaf.
Output:
[1]The code avoids duplicate insertion because:
if not is_leaf(root):
add_leaves(root)Left-Skewed Tree
1
/
2
/
3Boundary:
[1, 2, 3]Right-Skewed Tree
1
\
2
\
3Boundary:
[1, 3, 2]The right boundary is reversed.
Testing
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def run_tests():
s = Solution()
root = TreeNode(
1,
None,
TreeNode(2, TreeNode(3), TreeNode(4)),
)
assert s.boundaryOfBinaryTree(root) == [1, 3, 4, 2]
root = TreeNode(
1,
TreeNode(
2,
TreeNode(4),
TreeNode(5, TreeNode(7), TreeNode(8)),
),
TreeNode(
3,
None,
TreeNode(6, TreeNode(9), None),
),
)
assert s.boundaryOfBinaryTree(root) == [1, 2, 4, 7, 8, 9, 6, 3]
root = TreeNode(1)
assert s.boundaryOfBinaryTree(root) == [1]
root = TreeNode(1, TreeNode(2, TreeNode(3)))
assert s.boundaryOfBinaryTree(root) == [1, 2, 3]
root = TreeNode(1, None, TreeNode(2, None, TreeNode(3)))
assert s.boundaryOfBinaryTree(root) == [1, 3, 2]
print("all tests passed")
run_tests()| Test | Why |
|---|---|
| Root with right subtree | Checks missing left boundary |
| Mixed full tree | Standard complete boundary traversal |
| Single node | Checks duplicate prevention |
| Left-skewed tree | Checks left boundary logic |
| Right-skewed tree | Checks reversed right boundary |