LeetCode 545: Boundary of Binary Tree Solution in Python – A Step-by-Step Guide
Imagine you’re tracing the outline of a binary tree—like drawing a border around a family tree—capturing the root, all the leftmost nodes, the bottom leaves, and the rightmost nodes, in a clockwise loop, such as collecting [1, 2, 4, 7, 8, 5, 3] from a tree. That’s the fascinating challenge of LeetCode 545: Boundary of Binary Tree, a medium-to-hard problem that’s a fantastic way to practice tree traversal in Python. We’ll explore two solutions: the Best Solution, a DFS (depth-first search) with three-pass approach that’s efficient and clear, and an Alternative Solution, a BFS (breadth-first search) with level tracking that’s thorough but more complex. With detailed examples, clear code, and a friendly tone—especially for the DFS method—this guide will help you outline that tree, whether you’re new to coding or leveling up. Let’s trace that boundary and start exploring!
What Is LeetCode 545: Boundary of Binary Tree?
In LeetCode 545: Boundary of Binary Tree, you’re given the root of a binary tree, and your task is to return an array of node values representing the boundary in clockwise order: the root, all left boundary nodes (excluding leaves if already counted), all leaves from left to right, and all right boundary nodes (excluding leaves if already counted), in reverse order. For example, with a tree [1, 2, 3, 4, 5, null, null, 6, 7, null, 8], the boundary is [1, 2, 4, 6, 7, 8, 5, 3]. This problem builds on LeetCode 94: Binary Tree Inorder Traversal for traversal techniques and introduces boundary-specific logic.
Problem Statement
- Input: root (TreeNode)—root of a binary tree.
- Output: List[int]—node values on the boundary in clockwise order.
- Rules: Include root, left boundary (top-down), leaves (left-to-right), right boundary (bottom-up); avoid duplicates.
Constraints
- Number of nodes: 1 to 10⁴.
- Node values: -10³ to 10³.
Examples
- Input: root = [1,2,3,4,5,null,null,6,7,null,8]
- Output: [1,2,4,6,7,8,5,3]
- Tree: ``` 1 / \ 2 3 / \ 4 5 / \ 6 7 \ 8 ```
- Boundary: Root (1), Left (2, 4), Leaves (6, 7, 8), Right (5, 3).
- Input: root = [1,null,2,3,4]
- Output: [1,2,3,4]
- Tree: ``` 1 \ 2 / \ 3 4 ```
- Boundary: Root (1), Right (2), Leaves (3, 4).
- Input: root = [1]
- Output: [1]
- Single node.
Understanding the Problem: Tracing the Boundary
To solve LeetCode 545: Boundary of Binary Tree in Python, we need to collect node values along the tree’s boundary in clockwise order: root, left edge (excluding leaf overlaps), bottom leaves, and right edge (bottom-up, excluding overlaps). A naive approach might traverse the tree multiple times, but with up to 10⁴ nodes, we can optimize with a single-pass strategy or layered traversal. The boundary splits into distinct parts, requiring careful handling to avoid duplicates. We’ll explore:
- Best Solution (DFS with Three-Pass Approach): O(n) time, O(h) space (h = height)—efficient and clear.
- Alternative Solution (BFS with Level Tracking): O(n) time, O(w) space (w = width)—thorough but bulkier.
Let’s dive into the DFS solution with a friendly breakdown!
Best Solution: DFS with Three-Pass Approach
Why DFS Wins
The DFS with three-pass approach is the best for LeetCode 545 because it efficiently traverses the tree in O(n) time and O(h) space (recursion stack), splitting the boundary into three logical parts—left boundary, leaves, and right boundary—using tailored DFS methods to collect nodes in order while avoiding duplicates. It’s like sketching the tree’s outline with three careful strokes!
How It Works
Think of this as a tree-outline artist:
- Step 1: Left Boundary:
- DFS down left children, add nodes until leaf, exclude leaf if it’s a boundary leaf.
- Step 2: Leaves:
- DFS to collect all leaves (no children), left-to-right.
- Step 3: Right Boundary:
- DFS down right children, add nodes bottom-up, exclude leaf if already counted.
- Step 4: Combine:
- Root + left + leaves + right (adjusted for duplicates).
- Why It Works:
- Three passes target specific boundary parts.
- Flags and checks prevent overlap.
It’s like a tree-boundary tracer!
Step-by-Step Example
Example: root = [1, 2, 3, 4, 5, null, null, 6, 7, null, 8]
- Init: result = [].
- Step 1: Left Boundary:
- Start at 1, add 1 (root).
- Left (2), add 2, Left (4), add 4 (not leaf yet).
- Stop at 6 (leaf, exclude).
- Left = [1, 2, 4].
- Step 2: Leaves:
- DFS: 6 (leaf), 7 (leaf), 8 (leaf), 5 (leaf if no right child counted).
- Leaves = [6, 7, 8, 5].
- Step 3: Right Boundary:
- Right (3), add 3 (bottom-up).
- Right (5), add 5 (but leaf, exclude if counted), stop at 8 (leaf).
- Right = [3] (5 already in leaves).
- Step 4: Combine:
- Result = [1, 2, 4, 6, 7, 8, 5, 3].
- Result: [1, 2, 4, 6, 7, 8, 5, 3].
Code with Detailed Line-by-Line Explanation
Here’s the Python code, explained for beginners:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def boundaryOfBinaryTree(self, root: TreeNode) -> List[int]:
if not root:
return []
# Step 1: Initialize result with root
result = [root.val] if root.left or root.right else [root.val]
# Step 2: Left boundary (exclude leaves if counted)
def left_boundary(node):
if not node or (not node.left and not node.right):
return
result.append(node.val)
if node.left:
left_boundary(node.left)
else:
left_boundary(node.right)
# Step 3: Collect leaves
def leaves(node):
if not node:
return
if not node.left and not node.right:
result.append(node.val)
return
leaves(node.left)
leaves(node.right)
# Step 4: Right boundary (bottom-up, exclude leaves if counted)
def right_boundary(node):
if not node or (not node.left and not node.right):
return
if node.right:
right_boundary(node.right)
else:
right_boundary(node.left)
result.append(node.val)
# Step 5: Execute three passes
if root.left:
left_boundary(root.left)
leaves(root)
if root.right:
right_boundary(root.right)
return result
- Lines 12-13: Handle empty or single-node tree.
- Lines 16-23: Left DFS: Add non-leaf nodes top-down.
- Lines 26-33: Leaf DFS: Collect leaves left-to-right.
- Lines 36-43: Right DFS: Add non-leaf nodes bottom-up.
- Lines 46-50: Run passes, skip if no left/right subtree.
- Time Complexity: O(n)—visit each node once.
- Space Complexity: O(h)—recursion stack (h ≤ n).
It’s like a tree-edge painter!
Alternative Solution: BFS with Level Tracking
Why an Alternative Approach?
The BFS with level tracking solution uses breadth-first search to traverse level-by-level, identifying boundary nodes (leftmost, rightmost, leaves) in O(n) time and O(w) space (w = max width). It’s thorough but requires extra bookkeeping, making it a good alternative for BFS fans or when level-order intuition is preferred.
How It Works
Picture this as a tree-level scanner:
- Step 1: BFS with level info.
- Step 2: Track leftmost, rightmost, and leaves per level.
- Step 3: Combine boundary nodes in order.
It’s like a tree-level boundary mapper!
Step-by-Step Example
Example: root = [1, 2, 3, 4, 5]
- BFS:
- Level 0: [1], left = 1, right = 1.
- Level 1: [2, 3], left = 2, right = 3.
- Level 2: [4, 5], leaves = [4, 5].
- Combine: [1, 2, 4, 5, 3].
- Result: [1, 2, 4, 5, 3].
Code for BFS Approach
from collections import deque
class Solution:
def boundaryOfBinaryTree(self, root: TreeNode) -> List[int]:
if not root:
return []
if not root.left and not root.right:
return [root.val]
# Step 1: BFS with level tracking
queue = deque([(root, 0)])
levels = {}
while queue:
node, level = queue.popleft()
if level not in levels:
levels[level] = {'left': [], 'right': [], 'leaves': []}
if not node.left and not node.right:
levels[level]['leaves'].append(node.val)
else:
if not levels[level]['left']:
levels[level]['left'].append(node.val)
levels[level]['right'].append(node.val)
if node.left:
queue.append((node.left, level + 1))
if node.right:
queue.append((node.right, level + 1))
# Step 2: Build boundary
result = [root.val]
for level in sorted(levels.keys()):
if level > 0: # Exclude root level for left
result.extend(levels[level]['left'])
result.extend(levels[level]['leaves'])
if levels[level]['right'] and levels[level]['right'][-1] not in levels[level]['leaves']:
result.append(levels[level]['right'][-1])
return result
- Lines 11-24: BFS tracks leftmost, rightmost, leaves per level.
- Lines 27-33: Combine boundary in order, avoid duplicates.
- Time Complexity: O(n)—single BFS pass.
- Space Complexity: O(w)—queue and level storage.
It’s a BFS boundary tracker!
Comparing the Two Solutions
- DFS (Best):
- Pros: O(n), O(h), clear and efficient.
- Cons: Three-pass logic.
- BFS (Alternative):
- Pros: O(n), O(w), level-based.
- Cons: More space, bookkeeping.
DFS wins for simplicity and space!
Additional Examples and Edge Cases
- [1]: [1].
- [1, 2]: [1, 2].
- [1, null, 2, null, 3]: [1, 2, 3].
DFS handles them all!
Complexity Recap
- DFS: Time O(n), Space O(h).
- BFS: Time O(n), Space O(w).
DFS’s the elegance champ!
Key Takeaways
- DFS: Three-pass tracing—learn at Python Basics!
- BFS: Level-by-level scan.
- Trees: Boundaries are fun.
- Python: DFS or BFS, your pick!
Final Thoughts: Trace That Boundary!
LeetCode 545: Boundary of Binary Tree in Python is a delightful tree challenge. DFS with three-pass approach is your fast track, while BFS offers a level-based twist. Want more? Try LeetCode 94: Inorder Traversal or LeetCode 199: Right Side View. Ready to outline? Head to Solve LeetCode 545 on LeetCode and trace that boundary today!