Advertisement

Right/Left view of Binary Tree

Left & Right View of Binary Tree — Brute Force vs Optimal
Left View & Right View · Brute Force vs Optimal · C++

What you see from the sides

The left view of a binary tree is the set of nodes visible when looking at it from the left side — the first node at each level. The right view is the same from the right — the last node at each level. Two approaches: brute force (BFS + pick first/last) and optimal (DFS with level tracking).

01

Brute Force — the tree and the idea

Brute Force · O(N) time · O(N) space

The brute force approach uses BFS (level-order traversal) to collect all nodes level by level, then simply picks the first element of each level for the left view and the last element of each level for the right view. The tree used in the brute force code:

Brute Force Tree
1 2 3 4 5
levelOrder — BFS
1queue q;
2q.push(root);
3
4while (!q.empty()) {
5    int size = q.size();
6    vector<int> level;
7    for (int i = 0; i < size; i++) {
8        Node* node = q.front(); q.pop();
9        level.push_back(node->data);
10       if (node->left)  q.push(node->left);
11       if (node->right) q.push(node->right);
12   }
13   ans.push_back(level);
14}

Expected: Left View = [1, 2, 4] · Right View = [1, 3, 5]

02

Brute Force — BFS produces the level-order table

The BFS queue processes nodes level by level. At each iteration of the outer while loop, it captures the current queue size (= number of nodes at this level), then processes exactly that many nodes. Children are enqueued for the next level. The result is a 2D vector — one sub-vector per level.

Tree with levels marked
Level 0 Level 1 Level 2 1 2 3 4 5
Level Nodes (left → right) First (left view) Last (right view)
0 1 1 1
1 2 → 3 2 3
2 4 → 5 4 5
levelOrder — how each level is captured
1// Iteration 1: size=1 → process node 1 → level=[1] → push 2,3
2// Iteration 2: size=2 → process 2,3 → level=[2,3] → push 4,5
3// Iteration 3: size=2 → process 4,5 → level=[4,5] → no children
4
5ans = { {1}, {2,3}, {4,5} }   // 2D vector: one row per level
03

Brute Force — pick first and last of each level

Once the full level-order traversal is stored, extracting the views is trivial. The left view takes level[0] from each row; the right view takes level.back(). Simple — but it required storing the entire 2D traversal in memory, even though only one node per level is needed.

Left View · level[0] per row
1 2 4
{1} → first of [1]
{1,2} → first of [2,3]
{1,2,4} → first of [4,5]
Right View · level.back() per row
1 3 5
{1} → last of [1]
{1,3} → last of [2,3]
{1,3,5} → last of [4,5]
leftView & rightView — extraction
1// Left View: pick first element of each level
2for (auto& level : levels)
3    left.push_back(level[0]);     // first = leftmost
4
5// Right View: pick last element of each level
6for (auto& level : levels)
7    right.push_back(level.back());  // last = rightmost
04

Optimal — the tree and the idea

Optimal · O(N) time · O(H) space

The optimal approach uses DFS (pre-order traversal) with a clever trick: track the current depth (level) as you recurse. When you encounter a level for the first time (res.size() == level), the current node is the first one visited at that depth — add it to the result. For the left view, recurse left first; for the right view, recurse right first. No queue, no 2D vector — just a single result vector and the recursion stack.

Optimal Tree (skewed right chain)
L0 L1 L2 L3 L4 1 2 3 4 5 6
leftDFS — the key idea
1void leftDFS(TreeNode* node,
2             int level, vector<int>& res) {
3    if (!node) return;
4
5    // First time reaching this level?
6    if (res.size() == level)
7        res.push_back(node->val);
8
9    // Left first → leftmost node
10   leftDFS(node->left, level+1, res);
11   leftDFS(node->right, level+1, res);
12}

For rightDFS, swap the recursion order:
right first → rightmost node

Expected: Left View = [1, 2, 4, 5, 6] · Right View = [1, 3, 4, 5, 6]

05

Optimal — Left DFS trace (left child first)

The DFS visits nodes in pre-order, always going left first. The condition res.size() == level is true only the first time a depth is reached — and because left is explored first, that first node is the leftmost one at that level. Once a level is "claimed", all other nodes at the same depth are skipped.

DFS order: 1@L0 2@L1 4@L2 5@L3 6@L4 3@L1 (skip)
Left view — highlighted nodes
L0 L1 L2 L3 L4 1 2 3 4 5 6
DFS trace — left view
1 visit node 1 level=0 size=0 ADD → res={1}
2 visit node 2 level=1 size=1 ADD → res={1,2}
3 visit node 4 level=2 size=2 ADD → res={1,2,4}
4 visit node 5 level=3 size=3 ADD → res={1,2,4,5}
5 visit node 6 level=4 size=4 ADD → res={1,2,4,5,6}
6 visit node 3 level=1 size=5 SKIP (5≠1)
Left View result
1 2 4 5 6
06

Optimal — Right DFS trace (right child first)

Same logic, but the recursion order is swapped: right child first, then left. This means the first node encountered at each depth is the rightmost one. Node 3 is visited at level 1 before node 2, so 3 claims that level. But node 2's right chain (4, 5, 6) descends through levels 2-4 that node 3 couldn't reach (it has no children), so 4, 5, 6 are all claimed on the way back.

DFS order: 1@L0 3@L1 2@L1 (skip) 4@L2 5@L3 6@L4
Right view — highlighted nodes
L0 L1 L2 L3 L4 1 2 3 4 5 6
DFS trace — right view
1 visit node 1 level=0 size=0 ADD → res={1}
2 visit node 3 level=1 size=1 ADD → res={1,3}
3 visit node 2 level=1 size=2 SKIP (2≠1)
4 visit node 4 level=2 size=2 ADD → res={1,3,4}
5 visit node 5 level=3 size=3 ADD → res={1,3,4,5}
6 visit node 6 level=4 size=4 ADD → res={1,3,4,5,6}
Right View result
1 3 4 5 6
07

Brute Force vs Optimal — side by side

Both approaches produce the same result, but the optimal one is more memory-efficient. The brute force stores the entire level-order traversal (a 2D vector with all N nodes) just to pick one node per level. The optimal approach uses only a single result vector and the recursion stack — no extra storage for intermediate data.

Aspect Brute Force (BFS) Optimal (DFS)
Technique BFS level-order, then pick level[0] / level.back() DFS pre-order with res.size() == level check
Data structures queue + 2D vector (all levels stored) Single result vector + recursion stack
Traversal order Level by level (breadth-first) Depth-first (left-first for left view, right-first for right view)
Key insight First/last element of each level = left/right view First node visited at a new depth = view node (order determines which view)
Time complexity O(N) — every node visited once O(N) — every node visited once
Space complexity O(N) — queue + 2D vector stores all nodes O(H) — only recursion stack (H = tree height)
Two passes? Yes — BFS then iterate to extract (but reuses same levels vector) No — single DFS pass builds the result directly
Best for When you also need the full level-order traversal for other purposes When you only need the view — minimal memory, clean code
The critical difference — recursion order
// Left View: recurse LEFT first → leftmost node claims each level
leftDFS(node->left,  level + 1, res);   // ← left first
leftDFS(node->right, level + 1, res);

// Right View: recurse RIGHT first → rightmost node claims each level
rightDFS(node->right, level + 1, res);  // ← right first
rightDFS(node->left,  level + 1, res);

Both approaches find the side views

The brute force approach is intuitive — build the level-order table, pick the edges. The optimal approach is elegant — a single DFS pass where the recursion order determines which side you see. The key insight is that res.size() == level acts as a "first visit" detector: it's true exactly once per depth, and the node that triggers it is the view node for that level.

Brute Force Left View: 1 2 4
Brute Force Right View: 1 3 5
Optimal Left View: 1 2 4 5 6
Optimal Right View: 1 3 4 5 6
O(N)
Time — Both
O(N)
Space — Brute
O(H)
Space — Optimal
1
Pass — Optimal
Brute Force
O(N) time · O(N) space
BFS queue holds up to N/2 nodes (widest level). The 2D vector stores all N nodes. Two separate loops for left/right extraction, but both reuse the same levels vector.
Optimal
O(N) time · O(H) space
Recursion stack depth = tree height H. In the worst case (skewed tree) H = N, but for a balanced tree H = log N. The result vector has at most H entries (one per level). No intermediate storage needed.

Check for Symmetrical Binary Tree

Symmetric Tree — Visualizer
Mirror Recursion

Is the Tree Symmetric?

A tree is symmetric if its left and right subtrees are mirror images. The trick: compare left→left with right→right, and left→right with right→left. The recursion crosses over.

Blue = left subtree path
Amber = right subtree path
Green = matched ✓
Red = mismatch ✗
symmetric_tree.cpp
1bool isSymmetricUtil(Node* root1, Node* root2) {2 if (root1 == NULL || root2 == NULL) {3 return root1 == root2;4 }5 return (root1->data == root2->data)6 && isSymmetricUtil(root1->left, root2->right)7 && isSymmetricUtil(root1->right, root2->left);8}
Step 1 / 1
in progress…
Press “Next ▶” to begin the recursion…
What's Happening Now
We start by calling isSymmetricUtil(root->left, root->right) — comparing the left subtree (2) with the right subtree (2). Press Next ▶ to step through the mirrored recursion.
01
Mirror = cross the children
In isSameTree, you compared left with left and right with right. Here, it's root1->left with root2->right and root1->right with root2->left. The children are crossed — that's what "mirror image" means. The outer branches match each other, and the inner branches match each other.
02
Same base case as isSameTree
if (root1 == NULL || root2 == NULL) return root1 == root2 — if either is null, both must be null for symmetry. One null and one non-null means the tree has a node where its mirror doesn't — not symmetric. This is identical to the isSameTree base case.
03
Short-circuit && strikes again
The three conditions are joined by &&. If values differ, we return false immediately. If the outer mirror (left-left vs right-right) fails, we never check the inner mirror (left-right vs right-left). Watch the recursion skip the right call when the left returns false.
04
Root is never compared to itself
isSymmetric() skips the root entirely and calls isSymmetricUtil(root->left, root->right). The root is the center of the mirror — it doesn't need to match anything. Only its left and right subtrees need to be mirror images of each other.

Bottom view of a Binary Tree

Bottom View of Binary Tree — Step-by-Step Visualization
Bottom View of a Binary Tree · BFS + Vertical Lines · C++

What do you see from the bottom?

Imagine looking up at the tree from below. Nodes aligned on the same vertical line overlap — only the lowest one is visible. This algorithm uses BFS to assign each node a "vertical position" and keeps overwriting the map so the last (deepest) node at each position wins.

How vertical positions work

The root is at vertical position 0. Going left subtracts 1 (line - 1); going right adds 1 (line + 1). Nodes on the same vertical position form a column — like floors of a building seen from below.

Why BFS + map = bottom view

BFS visits nodes level by level (top to bottom). Each time a node is visited at a given vertical line, its value overwrites the previous one in the map. Since deeper nodes are visited later, the final value stored for each line is the bottom-most node — exactly the bottom view.

00

The tree and its vertical lines

The main() function builds this tree. Each node is labeled with its data value and assigned a vertical position. The dashed vertical guides show the columns — nodes in the same column will compete for visibility. Only the bottom-most node in each column survives.

Binary Tree with vertical positions
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 10 5 6

Note: nodes 10 (left subtree) and 9 both sit at line 0, and node 1 is also at line 0. They will compete — the deepest one wins.

01

Initialize — push root into the queue

The queue starts with the root node paired with its vertical position 0. The map mpp is empty. Everything is set for BFS to begin.

Tree · root queued
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
1 @0
Map (vertical → node)
mpp = {} — empty
bottomView — initialization
1if (root == NULL) return ans;
2map<int, int> mpp;
3queue<pairint>> q;
4q.push({root, 0});     // root at vertical position 0
02

Process node 1 (line 0) — first map entry

Pop (1, 0) from the queue. The map has no entry for line 0 yet, so this is a new insertion. Then push both children: (2, -1) and (3, +1).

Processing node 1
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
2 @-1 3 @+1
Map (vertical → node)
linevalueaction
01NEW
bottomView — BFS loop
1while (!q.empty()) {
2    auto it = q.front(); q.pop();
3    Node* node = it.first;  // node = 1
4    int line = it.second;   // line = 0
5    mpp[line] = node->data;  // mpp[0] = 1 (new entry)
6
7    if (node->left)  q.push({node->left,  line - 1});  // push (2, -1)
8    if (node->right) q.push({node->right, line + 1});  // push (3, +1)
9}
New entry mpp[0] = 1 → first node at vertical position 0
03

Process node 2 (line -1) — two new entries

Pop (2, -1). Line -1 is empty → new entry with value 2. Push children: (4, -2) and (10, 0) — note that node 10 also lands on line 0.

Processing node 2
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
3 @+1 4 @-2 10 @0
Map (vertical → node)
linevalueaction
-12NEW
01
bottomView — BFS loop
1    Node* node = it.first;  // node = 2
2    int line = it.second;   // line = -1
3    mpp[line] = node->data;  // mpp[-1] = 2 (new entry)
4
5    if (node->left)  q.push({node->left,  line - 1});  // push (4, -2)
6    if (node->right) q.push({node->right, line + 1});  // push (10, 0) ← same line as root!
New entry mpp[-1] = 2. Node 10 queued at line 0 — it will compete with node 1 later.
04

Process node 3 (line +1) — new entry

Pop (3, +1). Line +1 is empty → new entry with value 3. Push children: (9, 0) — another node at line 0 — and (10, +2).

Processing node 3
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
4 @-2 10 @0 9 @0 10 @+2
Map (vertical → node)
linevalueaction
-12
01
+13NEW
New entry mpp[+1] = 3. Two more nodes queued at line 0 — the battle for line 0 heats up.
05

Process node 4 (line -2) — new entry

Pop (4, -2). Line -2 is empty → new entry with value 4. Push child: (5, -1) — node 5 is at line -1, where node 2 currently sits.

Processing node 4
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
10 @0 9 @0 10 @+2 5 @-1
Map (vertical → node)
linevalueaction
-24NEW
-12
01
+13
New entry mpp[-2] = 4. Node 5 queued at line -1 — will it replace node 2?
06

Process node 10 (line 0) — first overwrite!

Pop (10, 0). Line 0 already has value 1 (from the root), but BFS overwrites it unconditionally: mpp[0] = 10. The root's value at line 0 is now gone — replaced by this deeper node. Node 10 is a leaf (left subtree version), so no children are pushed.

Processing node 10 (left subtree) — OVERWRITE
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
9 @0 10 @+2 5 @-1
Map (vertical → node)
linevalueaction
-24
-12
010 (was 1)OVERWRITE
+13
bottomView — the overwrite
1    Node* node = it.first;  // node = 10
2    int line = it.second;   // line = 0
3    mpp[line] = node->data;  // mpp[0] = 10  ← overwrites 1!
4
5    // node 10 is a leaf — no children to push
Overwrite! mpp[0]: 1 → 10. The root is no longer the bottom-most node at line 0.
07

Process node 9 (line 0) — second overwrite!

Pop (9, 0). Line 0 just got overwritten to 10, and now it's overwritten again: mpp[0] = 9. This is the key insight — BFS processes nodes level by level, so deeper nodes always come later and overwrite shallower ones. Node 9 is also a leaf.

Processing node 9 — OVERWRITE again
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 9 5 6
Queue (BFS)
10 @+2 5 @-1
Map (vertical → node)
linevalueaction
-24
-12
09 (was 10)OVERWRITE
+13
Overwrite again! mpp[0]: 10 → 9. But wait — there's still node 6 deeper down at line 0...
08

Process node 10 (line +2) — new entry

Pop (10, +2). Line +2 is empty → new entry. This node (right subtree's right child) is a leaf, so no children are pushed.

Processing node 10 (right subtree)
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
5 @-1
Map (vertical → node)
linevalueaction
-24
-12
09
+13
+210NEW
New entry mpp[+2] = 10. Only one node left in the queue.
09

Process node 5 (line -1) — overwrite node 2

Pop (5, -1). Line -1 currently has value 2, but node 5 is deeper (it's below node 4). Overwrite: mpp[-1] = 5. Push child: (6, 0) — one more contender for line 0!

Processing node 5 — OVERWRITE at -1
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
6 @0
Map (vertical → node)
linevalueaction
-24
-15 (was 2)OVERWRITE
09
+13
+210
Overwrite! mpp[-1]: 2 → 5. Node 5 is deeper than node 2. One more node in queue...
10

Process node 6 (line 0) — the final overwrite!

Pop (6, 0). Line 0 has been overwritten twice already (1 → 10 → 9), and now node 6 — the deepest node at line 0 — takes the crown: mpp[0] = 6. This is why the algorithm works: BFS guarantees that the last node visited at any vertical position is the bottom-most one. The queue is now empty.

Processing node 6 — FINAL overwrite at line 0
line -2 line -1 line 0 line +1 line +2 1 2 3 4 10 10 5 6
Queue (BFS)
empty — BFS complete
Map (vertical → node)
linevalueaction
-24
-15
06 (was 9)OVERWRITE
+13
+210
Final overwrite! mpp[0]: 9 → 6. Node 6 is the deepest at line 0. BFS is done.
11

Extract from map → result vector

The BFS loop is done. The map now holds the bottom-most node for each vertical position. Since std::map keeps keys sorted, iterating from -2 to +2 gives the bottom view in left-to-right order. The values are transferred to the result vector.

Vertical columns — bottom view (deepest node wins)
line -2
4
line -1
2
5
line 0
1
10
9
6
line +1
3
line +2
10
bottomView — extracting the result
1// map is sorted by key: -2, -1, 0, +1, +2
2for (auto it : mpp) {
3    ans.push_back(it.second);   // extract values in sorted key order
4}
5return ans;   // {4, 5, 6, 3, 10}
Bottom View: 4 5 6 3 10

The bottom view is 4 5 6 3 10

The algorithm visited all 9 nodes via BFS, assigning each a vertical position. Five vertical lines were discovered (-2, -1, 0, +1, +2). Line 0 saw the most action — four nodes competed for it, and node 6 (the deepest) won. The std::map kept everything sorted by key, so the final iteration produced the bottom view in left-to-right order.

Vertical LineNodes at this lineOverwritesBottom View Winner
-240 (new)4
-12, 51 (2→5)5
01, 10, 9, 63 (1→10→9→6)6
+130 (new)3
+2100 (new)10
9
Nodes Processed
5
Vertical Lines
4
Overwrites
5
Result Size
Time Complexity
O(N log N)
Each of the N nodes is pushed/popped from the queue once (O(1)), and inserted into the map once. Map insertion is O(log N) due to the balanced tree, giving O(N log N) total.
Space Complexity
O(N)
The queue holds up to O(N) nodes at once (worst case: a complete level). The map stores up to N entries (one per unique vertical line). The result vector also holds O(N) values.

Bottom Ad

Top Ad

Advertisement