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 11 – Tree patterns: depth-first

Treating a tree as a graph

Add parent links to a binary tree and search it like a graph: find every node at distance k from a target, and how long a message takes to reach all nodes.

  • Intermediate
  • 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 intro, Pro: 7 of 30 minutes free
Free: Two questions about one building · The brute force, and why it fails at this size · What to notice. With a pass: the other 11 sections and 3 interview answers, about 23 minutes.

What you will learn

  • Add parent links to search outwards from any node of a binary tree
  • Find all nodes at distance k from a target with a breadth-first search
  • Compute how long it takes to spread from one node to the whole tree
  • Solve both problems without a parent map in one recursive pass, and say what that costs
  • Extend the method to several sources, weighted edges and the farthest pair

Before you start

What you’ll learn · Before you start

What you will learn

  • Add parent links to search outwards from any node of a binary tree
  • Find all nodes at distance k from a target with a breadth-first search
  • Compute how long it takes to spread from one node to the whole tree
  • Solve both problems without a parent map in one recursive pass, and say what that costs
  • Extend the method to several sources, weighted edges and the farthest pair

Before you start

On this page

To find all nodes at distance k from a target in a binary tree, treat the tree as an undirected graph. One traversal records every node’s parent, so each node has up to three neighbours: its left child, its right child and its parent. A breadth-first search from the target then meets the nodes in order of distance, and the nodes of its k-th level are the answer. A set of nodes already reached keeps the search from walking straight back along a link. The same search also gives the time a spread from one node needs to reach all of them.

Two questions about one building

The rooms of a building form a binary tree: the entrance hall is the root, and every room leads on to at most two rooms, each with a unique number.

  • Rooms near an alarm. Given the alarm room and a number k, return every room exactly k corridors from it, in increasing order.
  • A message to every room. A message starts in one room and crosses one corridor a minute, in both directions. After how many minutes has every room heard it?

Limits: up to 10⁵ rooms, 0 ≤ k ≤ 10⁵, and a building that may be one long corridor.

The building as a tree of eleven rooms, entrance 20 at the top; alarm room 16 has a double border and rooms 4, 17 and 20 are dashed.203511422816431131817RLRLRLRLRL

The building; the rooms two corridors from the alarm in 16 are dashed

Text description of the diagram

The diagram shows eleven rooms as a binary tree, with the entrance hall, room 20, at the top. The corridors are drawn as plain lines without arrows, because they can be walked in both directions. Each line is marked L for the left room below or R for the right room below.

  • Room 20 leads to 11 on the left and 35 on the right.
  • Room 11 leads to 4, a dead end, and to 16. Room 16 leads to 13, a dead end, and to 18, which leads to 17.
  • Room 35 leads to 28, which leads on to 31 on its right, and to 42, a dead end.

The level-order list of this tree is [20, 11, 35, 4, 16, 28, 42, None, None, 13, 18, None, 31, None, None, None, None, 17].

The alarm is in room 16, drawn with a double border. The rooms exactly two corridors away from it are dashed: 17, two corridors down through 18; 4, up to 11 and down again; and 20, two corridors up. A search that only looks below 16 finds 17 alone.

With the alarm in 16, the rooms 2 corridors away are 4, 17 and 20, only one of them below 16; at k = 3 from 31 they are 20 and 42. A message from 17 needs 7 minutes, the last room being 31; from the entrance it needs 4.

The brute force, and why it fails at this size

Measure each room on its own: search from the entrance for the room and for the alarm, and count the corridors where the two paths part. For the message, walk the whole building once a minute, passing it along every corridor next to a room that has it. Both are correct, but the first costs one O(n) search per room and the second one walk per minute, with n minutes on a long corridor. The run counts 501,500 and 999,000 rooms visited on a corridor of 1,000; at 10⁵ rooms that grows to about 5 × 10⁹ and 10¹⁰.

Each room measured on its own, and a walk a minute Python · brute/brute_force.py
"""Rooms at distance k, and the time for news to reach every room, by brute force; the rooms visited are counted."""
from collections import deque


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


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


visits = 0


def path_to(root, room):
    """The rooms from the entrance down to room, found by searching the building from the top."""
    global visits
    above, stack = {root: None}, [root]
    while stack:
        node = stack.pop()
        visits += 1
        if node.val == room:
            path = []
            while node:
                path.append(node.val)
                node = above[node]
            return path[::-1]
        for child in (node.right, node.left):
            if child:
                above[child] = node
                stack.append(child)
    return None


def every_room(root):
    out, stack = [], [root]
    while stack:
        node = stack.pop()
        out.append(node.val)
        stack += [c for c in (node.right, node.left) if c]
    return out


def at_distance_brute(root, target, k):
    """For every room, find both paths from the entrance and measure the distance where they part."""
    to_target = path_to(root, target)
    found = []
    for room in every_room(root):
        to_room = path_to(root, room)
        shared = 0
        while shared < min(len(to_room), len(to_target)) and to_room[shared] == to_target[shared]:
            shared += 1
        if (len(to_room) - shared) + (len(to_target) - shared) == k:
            found.append(room)
    return sorted(found)


def minutes_brute(root, start):
    """Each minute, walk the whole building once and pass the news across every corridor next to someone who knows."""
    global visits
    heard, minutes, total = {start}, 0, len(every_room(root))
    while len(heard) < total:
        newly = set()
        stack = [(root, None)]
        while stack:
            node, above = stack.pop()
            visits += 1
            near = [c.val for c in (node.left, node.right) if c] + ([above] if above is not None else [])
            if node.val not in heard and any(r in heard for r in near):
                newly.add(node.val)
            stack += [(c, node.val) for c in (node.right, node.left) if c]
        heard |= newly
        minutes += 1
    return minutes


building = build([20, 11, 35, 4, 16, 28, 42, None, None, 13, 18, None, 31, None, None, None, None, 17])
for target, k in [(16, 2), (31, 3), (16, 0)]:
    visits = 0
    print(f"rooms {k} corridors from {target}: {at_distance_brute(building, target, k)}, {visits} rooms visited")
for start in (17, 20):
    visits = 0
    print(f"minutes for news from {start} to reach every room: {minutes_brute(building, start)}, {visits} rooms visited")

line = node = Node(0)
for v in range(1, 1000):
    node.left = Node(v)
    node = node.left
visits = 0
print(f"a line of 1,000, news from the far end: {minutes_brute(line, 999)} minutes, {visits:,} rooms visited")
visits = 0
at_distance_brute(line, 999, 10)
print(f"a line of 1,000, rooms 10 corridors from the far end: {visits:,} rooms visited")

Output

rooms 2 corridors from 16: [4, 17, 20], 70 rooms visited
rooms 3 corridors from 31: [20, 42], 76 rooms visited
rooms 0 corridors from 16: [16], 70 rooms visited
minutes for news from 17 to reach every room: 7, 77 rooms visited
minutes for news from 20 to reach every room: 4, 44 rooms visited
a line of 1,000, news from the far end: 999 minutes, 999,000 rooms visited
a line of 1,000, rooms 10 corridors from the far end: 501,500 rooms visited

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

What to notice

The way between two rooms may climb towards the entrance before it turns down again, and the tree records only the way down.

  • A parent map gives every node a link to its parent, after one traversal.
  • A breadth-first search over left, right and parent links meets nodes in order of distance.
  • A visited set stops the search from walking back along the link it came by.
  • The number of levels after the first is how long a spread from that node takes.
  • One recursive pass can report each node’s distance to the target instead, with no parent map.
  • Several sources at once start the same search with every source on its first level.
  • Why the levels are exact distances, with an invariant
  • How long until every room hears
  • Without a parent map: one recursive pass
  • The code in five languages
  • Edge cases and common bugs
  • Variants: within k, several alarms, lengths, the farthest pair
  • Follow-up questions
  • Exercise
    • Minutes until a message reaches every room
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why does a breadth-first search over a binary tree with parent links need a set of visited nodes, when a search from the root down does not?
    • Find all nodes at distance k from a target in a binary tree without a parent map and without changing the nodes. What does each recursive call return, how are the answers collected, and how much memory does it use?
    • News starts with several employees of an org chart at the same moment and crosses one reporting link a minute. How do you compute when the last person hears it, and how does the cost change as the number of starting points grows from 1 to n?
    • A recursive distance-k solution passes small tests but crashes on a chain of 10⁵ nodes in Python and in JavaScript. Explain the failure, and rewrite the approach so that no call stack grows with the height of the tree.
  • Key takeaways

8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 21 minutes

Opening this part with your 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).

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.