222 lines
9.6 KiB
YAML
222 lines
9.6 KiB
YAML
title: Binary Tree Right Side View
|
|
slug: binary-tree-right-side-view
|
|
difficulty: medium
|
|
leetcode_id: 199
|
|
leetcode_url: https://leetcode.com/problems/binary-tree-right-side-view/
|
|
categories:
|
|
- trees
|
|
patterns:
|
|
- slug: bfs
|
|
is_optimal: true
|
|
- slug: dfs
|
|
is_optimal: false
|
|
- slug: tree-traversal
|
|
is_optimal: false
|
|
|
|
function_signature: "def right_side_view(root: TreeNode) -> list[int]:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { root: [1, 2, 3, null, 5, null, 4] }
|
|
expected: [1, 3, 4]
|
|
- input: { root: [1, 2, 3, 4, null, null, null, 5] }
|
|
expected: [1, 3, 4, 5]
|
|
- input: { root: [1, null, 3] }
|
|
expected: [1, 3]
|
|
hidden:
|
|
- input: { root: [] }
|
|
expected: []
|
|
- input: { root: [1] }
|
|
expected: [1]
|
|
- input: { root: [1, 2, null] }
|
|
expected: [1, 2]
|
|
- input: { root: [1, 2, 3, 4, 5, 6, 7] }
|
|
expected: [1, 3, 7]
|
|
- input: { root: [1, 2, null, 3, null, 4, null, 5] }
|
|
expected: [1, 2, 3, 4, 5]
|
|
- input: { root: [0, -1, 100] }
|
|
expected: [0, 100]
|
|
|
|
description: |
|
|
Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return *the values of the nodes you can see ordered from top to bottom*.
|
|
|
|
constraints: |
|
|
- The number of nodes in the tree is in the range `[0, 100]`
|
|
- `-100 <= Node.val <= 100`
|
|
|
|
examples:
|
|
- input: "root = [1,2,3,null,5,null,4]"
|
|
output: "[1,3,4]"
|
|
explanation: "Standing on the right side, you see node 1 at level 0, node 3 at level 1 (it's rightmost), and node 4 at level 2 (it's rightmost even though it's a child of node 3)."
|
|
- input: "root = [1,2,3,4,null,null,null,5]"
|
|
output: "[1,3,4,5]"
|
|
explanation: "At each level, you see the rightmost node: 1, then 3, then 4 (left child of 2, but rightmost at that level since 3 has no children at that depth), then 5."
|
|
- input: "root = [1,null,3]"
|
|
output: "[1,3]"
|
|
explanation: "The tree only has right children, so you see both nodes from the right side."
|
|
- input: "root = []"
|
|
output: "[]"
|
|
explanation: "An empty tree has no nodes to see."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're standing to the right of a building where each floor has rooms arranged left to right. From your vantage point, you can only see the **rightmost room on each floor** — the other rooms are hidden behind it.
|
|
|
|
A binary tree works similarly when viewed from the right side. At each *level* (or depth) of the tree, only the **rightmost node** is visible. All other nodes at that level are obscured.
|
|
|
|
The key insight is that "right side view" means collecting **one node per level** — specifically, the last node encountered when traversing that level from left to right. This naturally suggests a **level-order traversal** (BFS) where we process nodes level by level and remember the last node at each level.
|
|
|
|
Alternatively, with DFS, we can traverse the tree visiting the right subtree before the left. The first node we encounter at each new depth becomes the visible node from the right.
|
|
|
|
approach: |
|
|
We solve this using a **Level-Order Traversal (BFS)** approach:
|
|
|
|
**Step 1: Handle edge case**
|
|
|
|
- If `root` is `None`, return an empty list — there are no nodes to see
|
|
|
|
|
|
|
|
**Step 2: Initialise data structures**
|
|
|
|
- `result`: Empty list to store the right side view
|
|
- `queue`: Initialise with the root node for BFS traversal
|
|
|
|
|
|
|
|
**Step 3: Process level by level**
|
|
|
|
- While the queue is not empty, determine the number of nodes at the current level (`level_size`)
|
|
- Iterate through all nodes at this level
|
|
- For each node, add its children (left then right) to the queue for the next level
|
|
- The **last node** processed in each level is the rightmost — add its value to the result
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- After processing all levels, `result` contains the right side view from top to bottom
|
|
|
|
|
|
|
|
The BFS approach naturally processes nodes left-to-right at each level, so the last node we see at each level is exactly what we'd see from the right side.
|
|
|
|
common_pitfalls:
|
|
- title: Confusing Right Side View with Right Children Only
|
|
description: |
|
|
A common mistake is thinking the right side view only includes nodes that are right children. This is incorrect.
|
|
|
|
Consider a tree where node 2 is a left child but has no sibling on the right at its level — node 2 would be visible from the right side. For example, in `[1,2,null]`, both 1 and 2 are visible from the right even though 2 is a left child.
|
|
|
|
The right side view includes the **rightmost node at each level**, regardless of whether it's a left or right child.
|
|
wrong_approach: "Only collecting right children"
|
|
correct_approach: "Collecting the last node at each level via BFS"
|
|
|
|
- title: Not Processing Full Levels in BFS
|
|
description: |
|
|
When using BFS, you must process **all nodes at a level** before moving to the next level. A common bug is to simply take the last element from the queue without tracking level boundaries.
|
|
|
|
The fix is to record `level_size = len(queue)` at the start of each level and iterate exactly that many times. This ensures you correctly identify where each level ends.
|
|
wrong_approach: "Processing queue without tracking level boundaries"
|
|
correct_approach: "Using level_size to process exactly one level at a time"
|
|
|
|
- title: Wrong DFS Order
|
|
description: |
|
|
When using DFS for this problem, you must visit the **right subtree before the left**. If you visit left first, the first node you see at each depth will be the leftmost, not the rightmost.
|
|
|
|
The DFS approach works by recording the first node encountered at each new depth — so visiting right-first ensures we see the right side view.
|
|
wrong_approach: "DFS visiting left subtree before right"
|
|
correct_approach: "DFS visiting right subtree before left"
|
|
|
|
key_takeaways:
|
|
- "**Level-order traversal** (BFS) is ideal when you need to process nodes level by level or find the first/last node at each depth"
|
|
- "**Right side view = rightmost at each level**, not just right children — understanding this distinction is crucial"
|
|
- "**DFS alternative**: Visit right-before-left and track depth to achieve the same result with O(h) space instead of O(w)"
|
|
- "This pattern extends to **left side view** (take first node per level) and other level-based tree problems"
|
|
|
|
time_complexity: "O(n). We visit each node exactly once during the BFS traversal."
|
|
space_complexity: "O(w) where w is the maximum width of the tree. In the worst case (a complete binary tree), the last level can have up to n/2 nodes, so this is O(n) in the worst case."
|
|
|
|
solutions:
|
|
- approach_name: BFS Level-Order Traversal
|
|
is_optimal: true
|
|
code: |
|
|
from collections import deque
|
|
from typing import Optional
|
|
|
|
class TreeNode:
|
|
def __init__(self, val=0, left=None, right=None):
|
|
self.val = val
|
|
self.left = left
|
|
self.right = right
|
|
|
|
def right_side_view(root: Optional[TreeNode]) -> list[int]:
|
|
# Edge case: empty tree
|
|
if not root:
|
|
return []
|
|
|
|
result = []
|
|
queue = deque([root])
|
|
|
|
while queue:
|
|
# Number of nodes at current level
|
|
level_size = len(queue)
|
|
|
|
for i in range(level_size):
|
|
node = queue.popleft()
|
|
|
|
# Add children for next level (left before right)
|
|
if node.left:
|
|
queue.append(node.left)
|
|
if node.right:
|
|
queue.append(node.right)
|
|
|
|
# Last node in level is the rightmost (visible from right)
|
|
if i == level_size - 1:
|
|
result.append(node.val)
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Every node is visited exactly once.
|
|
|
|
**Space Complexity:** O(w) — The queue holds at most one level of nodes. For a complete binary tree, the widest level has ~n/2 nodes.
|
|
|
|
We use BFS to traverse level by level. At each level, we process all nodes left-to-right, adding their children to the queue. The last node we process at each level is the rightmost, which is what we'd see from the right side.
|
|
|
|
- approach_name: DFS Right-First Traversal
|
|
is_optimal: false
|
|
code: |
|
|
from typing import Optional
|
|
|
|
class TreeNode:
|
|
def __init__(self, val=0, left=None, right=None):
|
|
self.val = val
|
|
self.left = left
|
|
self.right = right
|
|
|
|
def right_side_view(root: Optional[TreeNode]) -> list[int]:
|
|
result = []
|
|
|
|
def dfs(node: Optional[TreeNode], depth: int) -> None:
|
|
if not node:
|
|
return
|
|
|
|
# First time reaching this depth? This node is visible from right
|
|
if depth == len(result):
|
|
result.append(node.val)
|
|
|
|
# Visit right subtree first to see rightmost nodes first
|
|
dfs(node.right, depth + 1)
|
|
dfs(node.left, depth + 1)
|
|
|
|
dfs(root, 0)
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Every node is visited exactly once.
|
|
|
|
**Space Complexity:** O(h) — The recursion stack uses space proportional to the tree height. For a balanced tree h = log(n), for a skewed tree h = n.
|
|
|
|
We use DFS, visiting the right subtree before the left. The first time we reach a new depth, that node must be the rightmost at that level (since we explore right-first). We track this by comparing `depth` with `len(result)`.
|
|
|
|
This approach uses less space than BFS for tall, narrow trees but more space for short, wide trees.
|