Topics › Binary Trees › Medium Problems
Right/Left View of BT
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Imagine standing to the right of a binary tree and looking at it. On every level you see exactly one node, the rightmost one; everything else on that level is hidden behind it. The right view is the list of those visible nodes from the top level to the bottom. The left view is the same idea seen from the left: the leftmost node of every level. Return both (as two separate methods). An empty tree has empty views.
Constraints (LeetCode 199, right view): up to 100 nodes, values in [-100, 100]. The Java file also runs a 100000-node complete tree.
Example 1
1
/ \
2 3
\ \
5 4
right view = [1, 3, 4]
left view = [1, 2, 5]
Level by level the nodes are [1], [2, 3], [5, 4]. The right view takes the last of each level, the left view the first.
Example 2
1
/ \
2 3
/
4
right view = [1, 3, 4]
left view = [1, 2, 4]
The common mistake is to think the right view means “keep following right children”. Here 3 has no children, yet 4 is still visible from the right, because nothing else on its level stands in front of it. The view is about levels, not about pointers.
Approach 1: Level order traversal (BFS)
Intuition. Since the answer is “one node per level”, traverse the tree level by level. A queue processes nodes in breadth-first order, and if we read the queue’s size at the start of each round, that size is exactly the number of nodes on the current level. While draining those nodes, the first one dequeued is the leftmost node of the level and the last one is the rightmost. Children are enqueued left before right, so the order inside each level is left to right.
Algorithm.
- If the root is
null, return an empty list. Otherwise enqueue the root. - While the queue is not empty:
- Let
sizebe the queue’s current size. - Dequeue
sizenodes. Record the node at positionsize - 1for the right view, or position0for the left view. - Enqueue each dequeued node’s left child, then its right child, if present.
- Let
- Return the recorded values.
static List<Integer> rightViewBfs(TreeNode root) {
return viewBfs(root, true);
}
static List<Integer> leftViewBfs(TreeNode root) {
return viewBfs(root, false);
}
static List<Integer> viewBfs(TreeNode root, boolean rightSide) {
List<Integer> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size(); // exactly the nodes of the current level
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (rightSide ? i == size - 1 : i == 0) out.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
}
return out;
}
Complexity. Time O(n): each node is enqueued and dequeued once. Space O(w), where w is the maximum number of nodes on one level. For a complete tree the last level holds about n / 2 nodes, so this is O(n) in the worst case.
Approach 2: Depth-first search with a depth counter
Intuition. We do not actually need whole levels; we only need the first node we meet at each depth, provided the traversal meets the visible node first. For the right view, use a depth-first order that goes to the right child before the left child (node, right, left). Take two nodes at the same depth. Their paths from the root split at some lowest common ancestor; one lies in that ancestor’s right subtree and the other in its left subtree. Since the right subtree is explored completely before the left subtree, the node further to the right is always visited first. So the first node seen at each depth is the rightmost one. For the left view, the same argument works with the usual node, left, right order.
How do we know a depth is new? Keep the answer list and compare the current depth with its size. When we reach a node at depth d, all of its ancestors (at depths 0 to d - 1) were visited earlier, and each of those depths already has an entry, so the list has at least d entries. It has exactly d entries if and only if no node at depth d has been seen yet. That single comparison, depth == out.size(), replaces a set of seen depths.
Algorithm.
- Call
viewDfs(root, 0). - In
viewDfs(node, depth): ifnodeisnull, return. Ifdepth == out.size(), append the node’s value. - Recurse into the child on the viewed side first, then the other child, both with
depth + 1.
static List<Integer> rightViewDfs(TreeNode root) {
List<Integer> out = new ArrayList<>();
viewDfs(root, 0, out, true);
return out;
}
static List<Integer> leftViewDfs(TreeNode root) {
List<Integer> out = new ArrayList<>();
viewDfs(root, 0, out, false);
return out;
}
static void viewDfs(TreeNode node, int depth, List<Integer> out, boolean rightSide) {
if (node == null) return;
if (depth == out.size()) out.add(node.val); // first node ever reached at this depth
TreeNode near = rightSide ? node.right : node.left; // the side we look from goes first
TreeNode far = rightSide ? node.left : node.right;
viewDfs(near, depth + 1, out, rightSide);
viewDfs(far, depth + 1, out, rightSide);
}
Dry run of the right view on Example 1:
| visit | depth | out.size() before |
recorded? | out after |
|---|---|---|---|---|
| 1 | 0 | 0 | yes | [1] |
| 3 | 1 | 1 | yes | [1, 3] |
| 4 | 2 | 2 | yes | [1, 3, 4] |
| 2 | 1 | 3 | no | [1, 3, 4] |
| 5 | 2 | 3 | no | [1, 3, 4] |
Complexity. Time O(n): every node is visited once. Space O(h) for the recursion, where h is the height.
Which one to use? The two approaches trade width against height. A balanced tree is wide (w about n / 2) but short (h about log n), so the DFS uses far less memory. A long chain is the reverse: the BFS queue never holds more than one node, while the DFS recursion goes n levels deep. In interviews both are accepted; the BFS is often easier to explain, and the DFS is shorter and shows a nice invariant.
Edge cases and pitfalls
- Empty tree. Both views are empty. The BFS returns before touching the queue (an
ArrayDequerefusesnull), and the DFS returns at the base case. - Hidden deep nodes. A node on a deep level can be visible even if it is in the “wrong” subtree, as
4in Example 2 (right view) or5in Example 1 (left view). Following only one side’s pointers fails on these trees. The tests also include a tree whose deepest node is reachable only through the right subtree but appears in the left view. - Reading the queue size once. In the BFS,
sizemust be captured before the inner loop. Callingqueue.size()in the loop condition would include children that were just added and mix levels together. - Chains. For a pure left chain or right chain, both views contain every node.
- Duplicates and negatives. Values are only copied, never compared, so they need no special care.
Related problems
- Right/Left View of Binary Tree is the same problem in the A2Z sheet.
- Level Order Traversal is the BFS that Approach 1 is built on.
- Top View of BT and Bottom view of BT look at the tree from above and below, grouping by column instead of level.
- Boundary Traversal combines a left-edge walk, the leaves, and a right-edge walk.
- Populating Next Right Pointers in Each Node links each level left to right; the right view is the set of nodes whose
nextstaysnull.