Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 12 – Tree patterns: breadth-first

Side views and zigzag level order

Return the left and right side views of a binary tree, its zigzag level order and the level with the largest sum, all from one level-order pass.

  • Beginner
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: The problem: a drone show filmed from both sides · One pass of the level-order template · Practise: the best row. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Find the left and right side views of a binary tree from the first and last node of each level
  • Generate zigzag level order by writing every other level backwards, straight into place
  • Find the level with the largest sum, keeping the smallest level on a tie and handling sums below zero
  • Compare the level-order pass with a depth-first search that visits the right child first

Before you start

What you’ll learn · Before you start

What you will learn

  • Find the left and right side views of a binary tree from the first and last node of each level
  • Generate zigzag level order by writing every other level backwards, straight into place
  • Find the level with the largest sum, keeping the smallest level on a tie and handling sums below zero
  • Compare the level-order pass with a depth-first search that visits the right child first

Before you start

On this page

The side views of a binary tree keep one node per level: the right side view the last node of each level, as a camera far to the right would record it, and the left side view the first. Zigzag level order reads the first level from left to right, the next from right to left, and so on. Both come from the level-order template, which hands over each level complete and in left-to-right order, and the same pass can find the level with the largest sum. Below are an original problem with all three tasks, the obvious method priced at 100,000 nodes, and the one-pass idea.

The problem: a drone show filmed from both sides

The problem

A drone show flies a formation stored as a binary tree. The lead drone is row 0; a drone’s left follower flies one row lower and to its left, its right follower one row lower and to its right. Each drone reports its battery reserve, a whole number from −1,000 to 1,000 that is below zero when it holds less than planned. Write three functions:

  1. side_views(root): cameras far to the left and far to the right record, on each row, only the drone nearest to them. Return both lists of reserves, from row 0 down.
  2. zigzag(root): the commentator names the drones row by row, turning around at the end of each row: row 0 from left to right, row 1 from right to left, and so on. Return the rows in that order.
  3. best_level(root): the row whose reserves add up to the most, the smallest such row on a tie, or −1 for an empty formation.

A formation has up to 100,000 drones.

In the diagram’s formation, [4, -2, 7, 3, -8, None, 5, 6] in level order, the left camera records 4, −2, 3 and 6 and the right camera 4, 7, 5 and 6: no drone on the right reaches row 3. The zigzag rows are 4; 7, −2; 3, −8, 5; and 6, and row 3 is best, with 6. In [-3, -1, -2] both rows total −3, so row 0 wins.

The example formation by rows: 4; −2 and 7; 3, −8 and 5; 6. Each row shows its zigzag direction and total, and what each camera sees.row 0 →total 44row 1 ←total 5−27row 2 →total 03−85row 3 ←total 66

The example formation, one row at a time

Text description of the diagram

The formation has seven drones, drawn from the top down with arrows from each drone to its followers. The lead drone, 4, is alone on row 0. Its left follower is −2 and its right follower is 7, on row 1. On row 2, −2 leads 3 and −8, and 7 has only a right follower, 5. On row 3, 3 has a left follower, 6, which is the only drone on that row.

The text at the left of each row gives the direction in which the zigzag order reads the row, and the row's total:

  1. Row 0 is read left to right; its total is 4.
  2. Row 1 is read right to left, so it comes out as 7, then −2; its total is 5.
  3. Row 2 is read left to right: 3, −8, 5; its total is 0.
  4. Row 3 is read right to left; its only drone is 6, so its total is 6.

A double border marks the drone that a camera far to the right records on each row: 4, 7, 5 and 6, the right view. A dashed border marks the drone that a camera far to the left records: 4, −2, 3 and 6, the left view. The right view ends with 6 although 6 is a follower on the left side, because no drone on the right reaches row 3. The largest total is 6, on row 3, so the best row is 3.

The brute force: rescan the formation for every row

Without a queue, the direct method finds each row on its own: for row r, walk the formation from the lead drone, no deeper than row r, and collect that row’s drones from left to right. Each walk visits every drone on rows 0 to r. That is cheap on a balanced formation, but a line of drones has a row for every drone.

Rows found by rescanning, with the visits counted

views/brute_force.py

"""Row by row, rescanning: for each row r, walk the whole formation again, no deeper than r, to collect that row.

All three answers come from the rows: the first and last value of each row are the side views, every other row read
backwards gives the zigzag order, and the row totals give the best row. The counter shows what that costs.
"""
from tree import build, formation, line

visits = 0


def row(root, r):
    """The values of row r, from left to right, found by a depth-first walk that stops below row r."""
    global visits
    values = []
    stack = [(root, 0)] if root is not None else []
    while stack:
        node, depth = stack.pop()
        visits += 1
        if depth == r:
            values.append(node.val)
            continue
        # Push the right child first, so that the left one is taken first: rows come out from left to right.
        if node.right is not None:
            stack.append((node.right, depth + 1))
        if node.left is not None:
            stack.append((node.left, depth + 1))
    return values


def rows_by_rescanning(root):
    rows = []
    while True:
        values = row(root, len(rows))
        if not values:  # an empty row: the formation has no more rows
            return rows
        rows.append(values)


def answers(root):
    rows = rows_by_rescanning(root)
    left_view = [r[0] for r in rows]
    right_view = [r[-1] for r in rows]
    zigzag = [r if d % 2 == 0 else r[::-1] for d, r in enumerate(rows)]
    totals = [sum(r) for r in rows]
    best = totals.index(max(totals)) if totals else -1  # index() finds the first, so the smallest row wins ties
    return left_view, right_view, zigzag, best


print(answers(build([4, -2, 7, 3, -8, None, 5, 6])))
print(answers(build([-3, -1, -2])))
print(answers(None))
print()

for n in (500, 1_000, 2_000):
    visits = 0
    rows_by_rescanning(line(n))
    print(f"a line of {n:,} drones: {visits:,} visits; n(n + 1)/2 + n = {n * (n + 1) // 2 + n:,}")
visits = 0
rows_by_rescanning(formation(4_095))
print(f"a complete formation of 4,095 drones in 12 rows: {visits:,} visits")
n = 100_000
print(f"a line of {n:,} drones would need {n * (n + 1) // 2 + n:,} visits")

Output

([4, -2, 3, 6], [4, 7, 5, 6], [[4], [7, -2], [3, -8, 5], [6]], 3)
([-3, -1], [-3, -2], [[-3], [-2, -1]], 0)
([], [], [], -1)

a line of 500 drones: 125,750 visits; n(n + 1)/2 + n = 125,750
a line of 1,000 drones: 501,500 visits; n(n + 1)/2 + n = 501,500
a line of 2,000 drones: 2,003,000 visits; n(n + 1)/2 + n = 2,003,000
a complete formation of 4,095 drones in 12 rows: 12,273 visits
a line of 100,000 drones would need 5,000,150,000 visits

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

views/tree.py

"""A binary tree node, a builder for a tree written as a list in level order, and two large test formations."""
from collections import deque


class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build(values):
    """The tree whose level-order list is `values`, with None where a child is missing."""
    if not values or values[0] is None:
        return None
    root = Node(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if values[i] is not None:
            node.left = Node(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = Node(values[i])
            queue.append(node.right)
        i += 1
    return root


def score(i):
    """The reserve of drone i of a test formation: a fixed scramble of -1,000 to 1,000, the same in every language."""
    return i * 7919 % 2001 - 1000


def formation(n):
    """A complete formation of n drones, filled row by row from the left; drone i is the i-th in level order."""
    nodes = [Node(score(i)) for i in range(n)]
    for i, node in enumerate(nodes):
        if 2 * i + 1 < n:
            node.left = nodes[2 * i + 1]
        if 2 * i + 2 < n:
            node.right = nodes[2 * i + 2]
    return nodes[0] if nodes else None


def line(n):
    """n drones in one line, each the right follower of the one before."""
    root = None
    for i in range(n - 1, -1, -1):
        root = Node(score(i), None, root)
    return root


def release(root):
    """Takes a tree apart one link at a time.

    Freeing a tree frees each node from inside the step that frees its parent. CPython copes with a line of 100,000
    nodes, but Pyodide, the Python behind the Run button, stopped when a line of 3,000 nodes was freed at once, so the
    examples take their long test trees apart with this loop when they are done with them.
    """
    stack = [root] if root is not None else []
    while stack:
        node = stack.pop()
        stack.extend(child for child in (node.left, node.right) if child is not None)
        node.left = node.right = None

The counts match n(n + 1)/2 + n, the last term being the walk that finds no row below the deepest. A line of 100,000 drones needs about 5 × 10⁹ visits, some 8 minutes of CPython at the 10⁷ simple steps a second of the lesson on constraints; balanced test trees never show it.

What to notice

Every answer is about one row as a whole: its first drone, its last drone, its order or its total. The level-order template delivers each row once, complete and from left to right, in O(n) for the whole tree, so one pass can answer all three.

One pass of the level-order template

Run the template of the module’s first lesson: when a row starts, read the queue’s length, take that many drones and add their followers at the back. Inside that loop:

  • Side views: the drone taken first is the left view’s, and the one taken last, at position size − 1, the right view’s.
  • Zigzag level order: on odd rows the drone taken at position i goes to position size − 1 − i, so the row comes out backwards with no second pass. Reversing the finished row, a deque filled from its left end and two stacks give the same order.
  • Best row: add up the row and keep the best total with its row, starting from the first row’s total, never 0, and replacing it only when a row is strictly larger, so a tie keeps the earlier row.

A depth-first search that visits the right child first also finds the right view: it keeps the first node met at each new depth. On the example formation, the pass goes like this:

Row Queue as the row starts Left Right Zigzag row Total Best so far
0 4 4 4 4 4 row 0
1 −2, 7 −2 7 7, −2 5 row 1
2 3, −8, 5 3 5 3, −8, 5 0 row 1
3 6 6 6 6 6 row 3

Practise: the best row

The exercise asks for best_level(root) in one pass. Its sample tests include rows that all add up to less than zero, a tie and a line of 1,500 drones.

Exercise · Easy · Python

The row of a formation with the largest total

Write best_level(root): the number of the row whose values add up to the most, counting the root's row as 0. If two or more rows share the largest total, return the smallest of their numbers. An empty formation (root is None) has no rows: return -1. Each node has val, left and right, and a missing child is None.

# 4 on row 0; -2 and 7 on row 1; 3, -8 and 5 on row 2; 6 on row 3 (the example formation of the lesson)
best_level(root)  # 3: the row totals are 4, 5, 0 and 6

Use one level-order pass with collections.deque. The sample tests include rows whose totals are all below zero, two rows that tie, and a line of 1,500 drones, deeper than a recursive solution can go by default.

Starter code · best.py

from collections import deque


def best_level(root):
    """The row with the largest total, the smallest row on ties; -1 for an empty formation."""
    # Replace this line with your code.
    return -1
The sample tests · test_best.py
from collections import deque

from best import best_level


class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build(values):
    """A tree from its values in level order, with None where a child is missing."""
    if not values or values[0] is None:
        return None
    root = Node(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if values[i] is not None:
            node.left = Node(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = Node(values[i])
            queue.append(node.right)
        i += 1
    return root


def test_example_formation():
    """the row totals are 4, 5, 0 and 6"""
    assert best_level(build([4, -2, 7, 3, -8, None, 5, 6])) == 3


def test_empty_formation():
    """no rows at all"""
    assert best_level(None) == -1


def test_single_drone_below_zero():
    """one row, whose total is below zero, is still the best row"""
    assert best_level(Node(-7)) == 0


def test_every_total_below_zero():
    """the totals are -5, -3 and -4, so row 1 is the best even though no total reaches 0"""
    assert best_level(build([-5, -1, -2, -1, -1, -1, -1])) == 1


def test_tie_keeps_the_earlier_row():
    """rows 0 and 1 both add up to -3"""
    assert best_level(build([-3, -1, -2])) == 0


def test_tie_after_a_better_row():
    """totals 1, 9, 2 and 9: row 1 wins, and row 3 only ties with it"""
    assert best_level(build([1, 4, 5, 1, None, None, 1, 9])) == 1


def test_rows_kept_apart():
    """a large value on row 2 belongs to row 2, not to row 1"""
    assert best_level(build([1, 2, 3, 40, -50, 6, 7])) == 1


def test_zero_totals():
    """totals 0, 0 and 1"""
    assert best_level(build([0, 5, -5, None, 1])) == 2


def test_wide_formation():
    """a complete formation of 4,095 drones whose values are their positions in level order: the last row wins"""
    nodes = [Node(i) for i in range(4095)]
    for i, node in enumerate(nodes):
        if 2 * i + 1 < len(nodes):
            node.left = nodes[2 * i + 1]
        if 2 * i + 2 < len(nodes):
            node.right = nodes[2 * i + 2]
    assert best_level(nodes[0]) == 11


def test_deep_line():
    """1,500 drones in one line, one per row: deeper than a recursive solution can go by default"""
    root = node = Node(-1)
    for i in range(1, 1500):
        child = Node(5 if i == 1200 else -1)
        if i % 2:
            node.right = child
        else:
            node.left = child
        node = child
    assert best_level(root) == 1200
A hint

Keep two numbers while the rows go by: the best total so far and the row it belongs to. What should the best total be before the first row, so that a formation whose rows all add up to less than zero still gets an answer, and which comparison lets the earlier row keep a tie?

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Paste brute_force.py and tree.py, change the list given to build(), and compare the rows with your own drawing of the formation.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 A formation is written in level order as [1, 2, 3, 4]: 1 leads 2 and 3, and 2 has a left follower, 4. What does the right-hand camera record?

    Choose one answer.

    Show the answer to question 1

    Answer: 1, 3, 4

    The camera records the last drone of every row. Row 0 is 1, row 1 ends with 3, and row 2 holds only 4. The right side stops at row 1, so the last row's drone is on the left side, and following right children alone would miss it.

  2. Question 2 of 5 Why does the best total start from the first row's total, and not from 0?

    Choose one answer.

    Show the answer to question 2

    Answer: Every row may add up to less than zero, and a best that starts at 0 would then never change

    With reserves below zero, every row total can be negative. A best of 0 is larger than all of them, so no row would ever replace it and the function would return −1 for a formation that has rows. Starting from the first row's total, or from a marker such as None, makes the first row the best until a larger total appears.

  3. Question 3 of 5 What is the zigzag order of the complete formation [1, 2, 3, 4, 5, 6, 7]?

    Choose one answer.

    Show the answer to question 3

    Answer: [[1], [3, 2], [4, 5, 6, 7]]

    Row 0 is read from left to right, row 1 from right to left, and row 2 from left to right again. Only the odd rows are written backwards, so the answer is [1], then 3 and 2, then 4, 5, 6 and 7.

  4. Question 4 of 5 Rows 1 and 3 of a formation share the largest total. Which comparison keeps row 1 as the answer?

    Choose one answer.

    Show the answer to question 4

    Answer: Replace the best only when a row's total is strictly larger than the best total

    With a strictly larger test, row 3 only ties with row 1 and does not replace it. Larger-or-equal would move the best to row 3, the later of the two, which breaks the rule that the smallest row wins a tie.

  5. Question 5 of 5 The rescanning brute force walks the formation once per row, plus once more to find that no row is left. How many drone visits does it make on a line of 6 drones?

    Type a number.

    Show the answer to question 5

    Answer: 27 visits

    The walk for row r visits the r + 1 drones of rows 0 to r, so the six rows cost 1 + 2 + 3 + 4 + 5 + 6 = 21 visits. The last walk, for a row 6 that does not exist, visits all 6 drones before it finds none. That is 21 + 6 = 27, the value of n(n + 1)/2 + n that the recorded run checks.

The one pass in five languages, with its invariant Pro: opens with a Pro, Premium or Ultimate pass

The three functions share one loop, and what changes between languages is how each spells the queue and the end of a row. Here they are in five languages with their recorded runs, the invariant that makes them right, and a stress test that catches four plausible slips.

In this part:

  • The code in Python, JavaScript, Java, C++ and Go
  • The invariant, and why the last drone taken is the rightmost
  • Four slips, caught by a stress test

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Zigzag order five ways, and depth-first views, measured Pro: opens with a Pro, Premium or Ultimate pass

There are several correct ways to write zigzag order and one tempting way that is quadratic, and the right view has a depth-first form with its own memory profile. This part times five zigzag methods on 262,143 drones and measures what the depth-first view keeps on its stack.

In this part:

  • Five ways to write zigzag order, timed on 262,143 drones
  • The right view by depth-first search, and Python’s recursion limit
  • Queue, recursion or a stack of your own: a trade-off table

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Edge cases, variants and follow-up questions Pro: opens with a Pro, Premium or Ultimate pass

Ties, totals below zero and lopsided formations are where an online judge’s unseen tests catch these functions. Here are the edge cases to test, four variants solved in a recorded run, the follow-up questions that interviews add, and a practice set with sample answers.

In this part:

  • Edge cases to test before you submit
  • Variants, with answers
  • Follow-up questions, answered
  • A practice set with sample answers

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    Why is the right side view of a binary tree the last node of each level, rather than the nodes reached by following right children from the root?

    A camera on the right sees, on every level, the node with nothing to its right, and that node sits in the left subtree whenever the right subtree is shorter. If the root's right child is a leaf while its left child has children, the deeper levels hold no node on the right at all, so the path of right children stops early and misses them. Level order produces each level complete and from left to right, so the last node taken on a level is the rightmost one on it, whichever subtree it belongs to. By the same argument the first node of each level is the left side view. A depth-first search gets the same answer when it visits the right child first and records the first node it meets at each new depth.

  2. Fresher to mid

    Your function that returns the level with the largest sum passes the sample tests, but hidden tests fail on a tree whose level sums are all negative and on a tree where two levels tie. What are the two likely bugs, how do you fix them, and which tests would you add?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A service receives trees only as level-order lists with null for missing children, such as [4, -2, 7, 3, -8, null, 5, 6], and must return the right side view of millions of them. Can you answer without building node objects? What does it cost in time and memory?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Drones join a formation one at a time, each as a new leaf under a drone already flying, and after every join the right-hand camera's view must be reported. Can you do better than a full level-order pass after each join? What do you store, and what does one join cost?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The side views are the first and the last node of each level, not the path of right children.
  • Zigzag level order writes every other row backwards, straight into place, and the best row starts from the first row’s total and changes only on a strictly larger one.
  • Rescanning the tree for every row costs n(n + 1)/2 + n visits on a line: about 5 × 10⁹ at 100,000 drones.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.