Intermediate

Mock Exam 3: Trees & Graphs

Three Medium-level problems testing BFS, DFS, and topological sort. These are among the most common interview topics at top tech companies. Target time: 60 minutes total.

Time Target: 60 Minutes
Problem 1: 20 min (Medium) | Problem 2: 20 min (Medium) | Problem 3: 20 min (Medium)
Start your timer before reading the problems. Do not look at solutions until time is up.

Problem 1: Binary Tree Level Order Traversal (Medium)

Given the root of a binary tree, return the level order traversal of its nodes' values (i.e., from left to right, level by level).

Examples

Input: root = [3, 9, 20, null, null, 15, 7]
        3
       / \
      9   20
         /  \
        15   7
Output: [[3], [9, 20], [15, 7]]

Input: root = [1]
Output: [[1]]

Input: root = []
Output: []

Constraints

  • Number of nodes: [0, 2000]
  • -1000 ≤ Node.val ≤ 1000

Solution: BFS with Queue

from collections import deque

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

def level_order(root):
    """BFS level order traversal.

    Time: O(n) - visit every node once
    Space: O(n) - queue holds up to n/2 nodes (last level)

    Use a queue. For each level, process all nodes
    currently in the queue, then add their children.
    The key is using len(queue) to know how many
    nodes belong to the current level.
    """
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        level = []

        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(level)

    return result

# DFS alternative (recursive):
def level_order_dfs(root):
    """DFS approach: track depth to assign levels.

    Time: O(n), Space: O(n)
    """
    result = []

    def dfs(node, depth):
        if not node:
            return
        if depth == len(result):
            result.append([])
        result[depth].append(node.val)
        dfs(node.left, depth + 1)
        dfs(node.right, depth + 1)

    dfs(root, 0)
    return result

# Test
root = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
print(level_order(root))  # [[3], [9, 20], [15, 7]]
💡
Pattern: BFS with level tracking. The trick is using len(queue) at the start of each iteration to process exactly one level. This pattern also solves: zigzag traversal, right side view, average of levels.

Problem 2: Number of Islands (Medium)

Given an m x n 2D grid of '1's (land) and '0's (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.

Examples

Input: grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
Output: 1

Input: grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
Output: 3

Constraints

  • m == grid.length, n == grid[i].length
  • 1 ≤ m, n ≤ 300
  • grid[i][j] is '0' or '1'

Solution: DFS Flood Fill

def num_islands(grid):
    """DFS flood fill to count connected components.

    Time: O(m * n) - visit every cell once
    Space: O(m * n) - recursion stack in worst case

    Scan the grid. When we find a '1', increment
    the island count and use DFS to "sink" the
    entire island by marking all connected '1's as '0'.
    """
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        # Boundary check and water check
        if (r < 0 or r >= rows or
            c < 0 or c >= cols or
            grid[r][c] == '0'):
            return

        grid[r][c] = '0'  # Mark as visited (sink)

        # Explore all 4 directions
        dfs(r + 1, c)
        dfs(r - 1, c)
        dfs(r, c + 1)
        dfs(r, c - 1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                dfs(r, c)

    return count

# BFS alternative:
def num_islands_bfs(grid):
    """BFS approach - avoids deep recursion stack.

    Time: O(m * n), Space: O(min(m, n)) for queue
    """
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])
    count = 0

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                grid[r][c] = '0'
                queue = deque([(r, c)])
                while queue:
                    cr, cc = queue.popleft()
                    for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
                        nr, nc = cr + dr, cc + dc
                        if (0 <= nr < rows and 0 <= nc < cols
                                and grid[nr][nc] == '1'):
                            grid[nr][nc] = '0'
                            queue.append((nr, nc))

    return count

Problem 3: Course Schedule (Medium)

There are numCourses courses labeled 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [a, b] means you must take course b before course a. Return True if you can finish all courses, or False if there is a cycle.

Examples

Input: numCourses = 2, prerequisites = [[1, 0]]
Output: True
Explanation: Take course 0 first, then course 1.

Input: numCourses = 2, prerequisites = [[1, 0], [0, 1]]
Output: False
Explanation: Circular dependency - impossible.

Constraints

  • 1 ≤ numCourses ≤ 2000
  • 0 ≤ prerequisites.length ≤ 5000
  • prerequisites[i].length == 2
  • All prerequisite pairs are unique

Solution: DFS Cycle Detection

from collections import defaultdict

def can_finish_dfs(numCourses, prerequisites):
    """DFS cycle detection in directed graph.

    Time: O(V + E) - visit each node and edge once
    Space: O(V + E) - adjacency list + recursion stack

    Build adjacency list. For each unvisited node,
    run DFS. Use 3 states:
    - 0: unvisited
    - 1: visiting (in current DFS path - cycle if revisited)
    - 2: visited (fully processed - safe)
    """
    graph = defaultdict(list)
    for course, prereq in prerequisites:
        graph[prereq].append(course)

    # 0=unvisited, 1=visiting, 2=visited
    state = [0] * numCourses

    def has_cycle(node):
        if state[node] == 1:  # Back edge = cycle
            return True
        if state[node] == 2:  # Already processed
            return False

        state[node] = 1  # Mark as visiting

        for neighbor in graph[node]:
            if has_cycle(neighbor):
                return True

        state[node] = 2  # Mark as visited
        return False

    for course in range(numCourses):
        if has_cycle(course):
            return False

    return True

Solution: BFS Topological Sort (Kahn's Algorithm)

def can_finish(numCourses, prerequisites):
    """BFS topological sort (Kahn's algorithm).

    Time: O(V + E)
    Space: O(V + E)

    Count in-degrees. Start with nodes that have
    in-degree 0 (no prerequisites). Process them,
    decrement neighbors' in-degrees. If all nodes
    are processed, no cycle exists.
    """
    graph = defaultdict(list)
    in_degree = [0] * numCourses

    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1

    # Start with courses that have no prerequisites
    queue = deque()
    for i in range(numCourses):
        if in_degree[i] == 0:
            queue.append(i)

    processed = 0
    while queue:
        node = queue.popleft()
        processed += 1

        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    # If all courses processed, no cycle
    return processed == numCourses

# Test
print(can_finish(2, [[1, 0]]))           # True
print(can_finish(2, [[1, 0], [0, 1]]))   # False
Exam Debrief: This exam tests three graph patterns: BFS level traversal (trees), DFS flood fill (grids), and topological sort (DAGs). If you can identify which pattern applies within the first 2 minutes of reading, you will save significant time.

Score Your Performance

ResultScoreNext Step
All 3 solved optimally in < 60 min⭐ ExcellentMove to Mock Exam 4
All 3 solved (any approach) in < 60 min👍 GoodReview topological sort, then Mock Exam 4
2 solved in 60 min🔄 DevelopingPractice BFS/DFS on 5 more graph problems
1 or 0 solved📚 Study MoreStudy graph traversal fundamentals, retry in 3 days