Back to DSA & Algorithms
DSA & Algorithms
easy
mid

How would you solve the zombie spread problem on a grid using multi source BFS?

Classic multi-source BFS on a grid (a.k.a. 'rotting oranges'). Seed a queue with ALL initial zombie cells at once, then BFS level by level — each level is one time unit. Track minutes as you process levels; mark cells visited as you infect them. At the end, if any human remains, return -1. Time O(rows×cols), space O(rows×cols).

6 min read·~25 min to think through

"Zombie spread on a grid" is the rotting oranges problem — the textbook multi-source BFS. Each minute, every zombie infects its 4-directional human neighbors; find the minutes until everyone is infected (or -1 if someone is unreachable).

Why multi-source BFS

All zombies spread simultaneously. If you BFS from one zombie at a time you'd get the wrong timing. Instead, seed the queue with every zombie cell up front — then a normal level-by-level BFS naturally models simultaneous spread, where each BFS level = one minute.

The algorithm

  1. Scan the grid once: push all zombie cells into the queue; count the humans.
  2. BFS level by level. For each level (minute):
  • Process every cell currently in the queue.
  • For each, infect 4-directional human neighbors → mark them zombie, decrement the human count, enqueue them.
  1. Increment minutes after a level that actually infected someone.
  2. At the end: if humans > 0, someone was unreachable → return -1. Else return minutes.

Implementation

js
function timeToInfect(grid) {
  const rows = grid.length, cols = grid[0].length;
  const queue = [];
  let humans = 0;

  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (grid[r][c] === 2) queue.push([r, c]);  // 2 = zombie
      else if (grid[r][c] === 1) humans++;        // 1 = human
    }
  }

  const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
  let minutes = 0;

  while (queue.length && humans > 0) {
    const size = queue.length;          // freeze this level's size
    for (let i = 0; i < size; i++) {
      const [r, c] = queue.shift();
      for (const [dr, dc] of dirs) {
        const nr = r + dr, nc = c + dc;
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
          grid[nr][nc] = 2;             // infect — also serves as 'visited'
          humans--;
          queue.push([nr, nc]);
        }
      }
    }
    minutes++;                          // one full level = one minute
  }

  return humans === 0 ? minutes : -1;
}

Complexity

  • Time: O(rows × cols) — every cell is enqueued and visited at most once.
  • Space: O(rows × cols) — worst case the queue holds all cells.

Key details interviewers probe

  • Why all sources at once — the crux. Single-source BFS gives wrong minute counts.
  • Freeze the level size (const size = queue.length) before the inner loop — otherwise you'd merge levels.
  • Mark visited by mutating the grid (grid[nr][nc] = 2) — no separate visited set needed; prevents double-counting.
  • The -1 case — humans walled off from any zombie.
  • Edge cases — no zombies but humans exist → -1; no humans at all → 0.
  • BFS not DFS — BFS gives shortest/level distance; DFS doesn't model simultaneous time steps.

Senior framing

The senior signal is naming the pattern immediately — "this is multi-source BFS, the rotting-oranges template" — and articulating why seeding all sources models simultaneous spread, plus the level-size-freeze detail. It generalizes: shortest distance from multiple starting points, fire spread, flood fill timing — all the same template.

Follow-up questions

  • Why must you seed the queue with all zombies before starting BFS?
  • How do you detect the unreachable-human (-1) case?
  • How would this change if zombies could move diagonally?
  • Why BFS and not DFS here?

Common mistakes

  • Doing single-source BFS from each zombie separately — wrong timing.
  • Not freezing queue.length before the level loop, merging minutes.
  • Forgetting the -1 case for unreachable cells.
  • Using a separate visited set instead of mutating the grid (works, just unnecessary).

Edge cases

  • No humans at all → 0 minutes.
  • Humans present but no zombies → -1.
  • Humans completely walled off → -1.
  • Empty grid.

Real-world examples

  • Rotting oranges (LeetCode 994), fire/flood spread simulations, shortest distance from multiple sources.

Related questions