DSA sheet · Trees · BFS (level order) pattern

Left View & Right View of a Binary Tree

Two problems in one video, because they differ by a single line. The teacher shows that a "view" is really a level order question: the left view is the first node of every level, and the right view is the last node of every level. She starts from the brute force (store every level, then pick one value), then saves the space by picking the right node while the level is being popped. The prerequisite is plain level order traversal.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

The tree node

given by LeetCode / GFG, don't write this in the solution
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Words we need

plain level order with BFS (the base we will edit)
from collections import deque

def level_order(root):
    ans = []
    if root is None:
        return ans
    queue = deque([root])
    while queue:
        size = len(queue)
        level = []
        for i in range(size):              # i = position inside the level
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        ans.append(level)
    return ans

Part A · Left view, brute force

GFG · Left View of Binary Tree

1The question in simple words

Given the root of a binary tree, return the values you would see if you stood on the left side of the tree, from top to bottom. This is the teacher's tree, which we use for the whole page:

           1           level 0
         /   \
        2     3        level 1
       /     / \
      7     4   5      level 2
           /     \
          6       7    level 3

Left view: [1, 2, 7, 6].

Doubt: isn't the left view just "keep going to the left child"?
→ No, and 6 is the proof. Following .left from the root gives 1 → 2 → 7 and then stops, because 7 has no children. But level 3 still exists, and its leftmost node, 6, is under 3's side. The view is about each level, not about one path.

2What the constraints tell us

3Intuition

Write down the plain level order for the tree:

levellevel order rowfirst value
0[1]1
1[2, 3]2
2[7, 4, 5]7
3[6, 7]6

The first column of values is exactly [1, 2, 7, 6]. The left view = the first element of every level. So if you know level order, this problem is almost done.

4Building the logic

  1. Get all rows with plain level order.
  2. Go through the rows and take row[0] from each one.

Why this is brute force

Only one value per level matters, yet we stored every value of every level (O(n) extra space) just to throw most of them away. Part B avoids storing them.

5Approach steps

  1. If the root is None, return [].
  2. Do level order and collect each row.
  3. Return the first value of each row.

6Code (Python)

Left view brute force: store rows, take row[0]
from collections import deque

def left_view_brute(root):
    if root is None:
        return []
    rows = []
    queue = deque([root])
    while queue:
        size = len(queue)
        level = []
        for i in range(size):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        rows.append(level)
    return [row[0] for row in rows]        # first of every level

7Code line by line

linewhat it means
if root is None: return []0 nodes allowed → empty view.
level.append(node.val)Stores every value. This is the waste.
rows.append(level)One full row kept in memory.
[row[0] for row in rows]Take the leftmost value of each level.

8Dry run

  1. Level order gives [[1], [2, 3], [7, 4, 5], [6, 7]].
  2. Firsts: 1, 2, 7, 6 → [1, 2, 7, 6].

9Complexity & remember

RememberLeft view = first of every level. Brute force stores every level just to read one value from each.

Part B · Left view, optimal BFS

1The question, and 2 the constraints

Same question and constraints as Part A: 0 nodes allowed (keep the empty check), up to 105 nodes (O(n) needed).

3Intuition: pick the node while popping

We don't need the rows at all. Inside the inner loop, i already tells us each node's position in its level. When i == 0, the node we just popped is the first of its level, so we add its value to the answer right then. All other nodes are popped but not stored.

4Building the logic: edit the level order code

Doubt: 3 is not in the answer. Why do we still push 3's children?
→ Because the next level's first node might be one of them. In our tree, 2 has a child (7), so 7 comes first on level 2. But imagine 2 had no children:
        1
       / \
      2   3
         / \
        4   5       ← now 4 is the first node of level 2
If we had skipped 3's children because 3 wasn't in the view, we'd never find 4, and the answer would be [1, 2] instead of [1, 2, 4]. So: store only index 0, but push the children of every node.

5Approach steps

  1. If the root is None, return [].
  2. Put the root in the queue.
  3. While the queue isn't empty: size = len(queue).
  4. For i from 0 to size − 1: pop a node; if i == 0, add its value to the answer; push its left and right children if they exist.
  5. Return the answer.

6Code (Python)

Left view with BFS: keep index 0 of every level
from collections import deque

def left_view(root):
    ans = []
    if root is None:                       # 0 nodes allowed
        return ans
    queue = deque([root])
    while queue:
        size = len(queue)
        for i in range(size):
            node = queue.popleft()
            if i == 0:                     # first node of this level
                ans.append(node.val)
            if node.left:                  # children of EVERY node
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
    return ans

On GFG the method is usually called LeftView(root) inside a class; the body is the same.

7Code line by line

linewhat it means
if root is None: return ansEmpty tree → empty view.
size = len(queue)How many nodes are on this level.
for i in range(size):Pop the level; i is the node's position in it.
if i == 0: ans.append(node.val)Position 0 = the node you'd see from the left. Store only that.
if node.left: ... if node.right: ...Push children for all nodes, even ones not in the view.
return ansAfter the while loop, the answer holds one value per level.

8Dry run on the teacher's tree

Yellow = the node at i == 0, which goes into the answer.

level 01size 1
i=0: pop 1 → ans [1] → push 2, 3
level 123size 2
i=0: pop 2 → ans [1, 2] → push 7 · i=1: pop 3 → skip → but push 4, 5
level 2745size 3
i=0: pop 7 → ans [1, 2, 7] (no children) · i=1: pop 4 → skip, push 6 · i=2: pop 5 → skip, push 7
level 367size 2
i=0: pop 6 → ans [1, 2, 7, 6] · i=1: pop 7 → skip
endqueue empty → return [1, 2, 7, 6]

9Complexity & remember

         1
       /   \
      2     3
     / \   / \
    7   8 4   5     ← all 4 sit in the queue together (≈ n/2)
Remember left viewLevel order without rows. Inside the level loop: if i == 0: ans.append(node.val). Push children of every node.

Part C · Right view with BFS

LeetCode 199 · Binary Tree Right Side View

1The question in simple words

Now stand on the right side and list what you see, top to bottom. Same tree:

           1           ← see 1
         /   \
        2     3        ← see 3 (2 is behind it)
       /     / \
      7     4   5      ← see 5 (7 and 4 are behind)
           /     \
          6       7    ← see 7 (6 is behind)

Right view: [1, 3, 5, 7]. In the level order rows [[1], [2, 3], [7, 4, 5], [6, 7]], these are the last values of each row.

Doubt: is the right view the same as following .right from the root?
→ Here it happens to be (1 → 3 → 5 → 7), but not in general. Take root 1 with children 2 and 3, where only 2 has a child, 4. The right path gives [1, 3], but from the right you still see 4 on level 2, because nothing else is on that level. The answer is [1, 3, 4]. Same lesson as the left view: think in levels.

2What the constraints tell us

Doubt: should I drop the if root is None check then?
→ Keep it. LeetCode 199's own constraints allow 0 to 100 nodes, so an empty tree really can come in there, and [] is the expected answer. The check costs nothing, and it makes the code safe for both versions.

3Intuition

In a level of length size, positions run from 0 to size − 1 (zero-based). Level 0 has 1 node, so its last position is 1 − 1 = 0. Level 1 has 2 nodes, so its last position is 2 − 1 = 1. The right view = the node at position size − 1 in every level.

4Building the logic: change one line

Take the left view code. The only change is the condition:

left viewright view
store the node wheni == 0i == size - 1
everything elseidentical: same queue, same size, children of every node pushed left then right

The teacher literally copies her left view code into the right view problem and changes 0 to size - 1.

Doubt: why still push children left first? Wouldn't right first be more natural for a right view?
→ The size − 1 test only works if each level sits in the queue in normal left-to-right order, so that the last popped node really is the rightmost. If you pushed right children first, the rightmost node would come out at i == 0 instead. That's a valid trick too (push right first, keep i == 0), but don't mix the two.

5Approach steps

  1. If the root is None, return [].
  2. BFS by levels with size = len(queue).
  3. For each popped node, if i == size - 1, add its value to the answer.
  4. Push the children of every node (left, then right).
  5. Return the answer.

6Code (Python)

Right view with BFS: keep index size - 1 of every level
from collections import deque

class Solution:
    def rightSideView(self, root):
        ans = []
        if root is None:                   # LeetCode allows 0 nodes
            return ans
        queue = deque([root])
        while queue:
            size = len(queue)
            for i in range(size):
                node = queue.popleft()
                if i == size - 1:          # last node of this level
                    ans.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        return ans

7Code line by line

linewhat it means
size = len(queue)Saved before popping. We compare against this value; len(queue) changes while we pop and push.
if i == size - 1: ans.append(node.val)The last position in the level = the node you see from the right.
queue.append(node.left / right)Same as before: every node's children, left then right.

8Dry run on the teacher's tree

Yellow = the node at i == size − 1.

level 01size 1, last index 0
i=0: pop 1 → 0 == 0 → ans [1] → push 2, 3
level 123size 2, last index 1
i=0: pop 2 → 0 ≠ 1 → skip, push 7 · i=1: pop 3 → 1 == 1 → ans [1, 3] → push 4, 5
level 2745size 3, last index 2
i=0: pop 7 → skip · i=1: pop 4 → skip, push 6 · i=2: pop 5 → 2 == 2 → ans [1, 3, 5] → push 7
level 367size 2, last index 1
i=0: pop 6 → skip · i=1: pop 7 → ans [1, 3, 5, 7]
endqueue empty → return [1, 3, 5, 7]

9Complexity & remember

Remember right viewLeft view code with one change: i == 0 becomes i == size - 1. That's why both are taught in one video.

Part D · Revision page

left viewright view
what you seefirst node of every levellast node of every level
condition inside the level loopi == 0i == size - 1
children pushedfor every node, left then right
teacher's tree[1, 2, 7, 6][1, 3, 5, 7]
time / spaceO(n) / O(n)O(n) / O(n)
brute forceoptimal
storesevery level as a listonly one value per level
extra space beyond the queueO(n)just the answer
If you remember only 5 lines 1. A view is a level question: one node per level.
2. Left view = position 0 in each level; right view = position size − 1.
3. Use plain level order, but don't build sublists.
4. Push children of every node, even ones you don't store.
5. Left view allows an empty tree: check root is None first.
Mistakes to avoid ✗ following only .left (or only .right) from the root
✗ pushing children only for the node you stored
✗ comparing with len(queue) instead of the saved size
✗ storing the node object instead of node.val
✗ forgetting the empty-tree check when 0 nodes are allowed

Extra (not in the video): the DFS idea

The teacher mentions trying both BFS and DFS when possible, but this video only codes BFS. For completeness: DFS can do it by carrying the level, and adding a node the first time a level is reached (level == len(ans)). Calling left first means the first node reached on each level is the leftmost one (left view). Calling right first gives the right view.

extra: right view with DFS (right child first)
class Solution:
    def rightSideView(self, root):
        ans = []
        self.dfs(root, 0, ans)
        return ans

    def dfs(self, root, level, ans):
        if root is None:
            return
        if level == len(ans):              # first node seen on this level
            ans.append(root.val)
        self.dfs(root.right, level + 1, ans)   # right first
        self.dfs(root.left, level + 1, ans)

Swap the two recursive calls (left first) and it becomes the left view.

test it yourself (paste under the left view and right view code)
root = TreeNode(1,
         TreeNode(2, TreeNode(7)),
         TreeNode(3, TreeNode(4, TreeNode(6)), TreeNode(5, None, TreeNode(7))))
small = TreeNode(1, TreeNode(2, TreeNode(4)), TreeNode(3))

print(left_view(root))                     # [1, 2, 7, 6]
print(Solution().rightSideView(root))      # [1, 3, 5, 7]
print(Solution().rightSideView(small))     # [1, 3, 4]
print(left_view(None))                     # []

Based on this video: Left View & Right View of Binary Tree | BFS