Start learning free

LESSON 472 OF 536 · GRAPHS

Rotting Oranges in JavaScript: O(m · n) Multi-Source BFS

Practice O(m · n) Read the full explanation

You are given a grid grid with m rows and n columns: an array of rows, where each row is an array of integers. Rows are numbered from 0, top to bottom, and columns from 0, left to right. Every cell holds one of three values:

  • 0: an empty cell,
  • 1: a fresh orange,
  • 2: a rotten orange.

Every minute, each fresh orange that is next to a rotten orange (up, down, left or right, not diagonally) becomes rotten.

Return the number of minutes that pass until no cell holds a fresh orange. If some orange can never rot, return -1.

 

Example 1:

Input: grid = [
  [2, 1, 1],
  [1, 1, 0],
  [0, 1, 1]
]
Output: 4
Explanation: The grid after each minute:
  start      minute 1   minute 2   minute 3   minute 4
  2 1 1      2 2 1      2 2 2      2 2 2      2 2 2
  1 1 0      2 1 0      2 2 0      2 2 0      2 2 0
  0 1 1      0 1 1      0 1 1      0 2 1      0 2 2
In minute 1 the two oranges next to the rotten one in the corner rot; in each later minute the
oranges next to those rot in turn. After minute 4 no orange is fresh.

Example 2:

Input: grid = [
  [2, 1, 1],
  [0, 1, 1],
  [1, 0, 1]
]
Output: -1
Explanation: The orange in the bottom left corner, [2, 0], has an empty cell above it and to
its right. Rot doesn't spread diagonally, so nothing ever reaches it: it stays fresh forever.

Example 3:

Input: grid = [[0, 2]]
Output: 0
Explanation: No orange is fresh at the start, so no minute has to pass.

 

Constraints:

  • 1 <= m, n <= 300
  • Every row has n values.
  • Every grid[r][c] is 0, 1 or 2.

Note:

Your algorithm should run in O(m · n) time and use O(m · n) extra space.

Simulating the grid minute by minute, with a scan of every cell in each minute, can cost O((m · n)2): think of a long winding line of oranges that the rot creeps along one cell per minute. The tutorial uses a BFS that starts from all the rotten oranges at once, as the BFS in 01 Matrix starts from all the zeros, and goes through it level by level: the oranges that rot in minute 1 are the first level, the oranges they rot are the second level, and so on, so the number of levels is the number of minutes. A count of the fresh oranges, lowered each time one rots, tells at the end whether some orange never rotted. Each cell joins the queue at most once.

STEP 1 OF 24
    Ctrl + Enter to check

    JAVASCRIPT · GRAPHS

    Rotting Oranges in JavaScript, explained

    Problem overview

    The method orangesRotting(grid) receives a grid of m rows and n columns, as an array of rows. Each cell holds 0 (empty), 1 (a fresh orange) or 2 (a rotten orange). Every minute, each fresh orange that touches a rotten one up, down, left or right becomes rotten. Diagonal neighbours don't count.

    The method returns the number of minutes until no cell holds a fresh orange, or -1 if some orange can never rot.

    In the first example, [[2,1,1],[1,1,0],[0,1,1]], the rot starts in the top-left corner and reaches the bottom-right corner in minute 4, so the answer is 4. In the second, [[2,1,1],[0,1,1],[1,0,1]], the orange in the bottom-left corner has empty cells above it and to its right, so the rot never reaches it and the answer is -1. For [[0,2]] no orange is fresh at the start, so the answer is 0.

    Approach: breadth-first search from every rotten orange at once

    A direct simulation scans the whole grid every minute. A long winding line of oranges lets the rot creep along one cell per minute, so the scans add up to O((m · n)²). The better view is that the rot spreads outward like a wave, and a breadth-first search (BFS) follows a wave naturally.

    The search starts from every rotten orange at the same time: they all go into a queue before anything is processed. Then it works level by level. The oranges that rot in minute 1 are the first level, the oranges they rot are the second, and so on, so the number of levels is the number of minutes. A counter of fresh oranges, lowered each time one rots, tells at the end whether some orange never rotted. Each cell joins the queue at most once, because it is marked rotten the moment it is added.

    The queue is an array with a head index that moves forward. Taking an element from the front with shift() would conceptually move every other element, while moving an index costs nothing.

    Step by step

    1. Store the grid size: rows is grid.length and cols is grid[0].length. Every row has the same length.
    2. Create queue, an empty array. It will hold [row, column] pairs of rotten oranges that are about to spread the rot.
    3. Declare fresh with let, starting at 0. It counts the fresh oranges.
    4. Scan the whole grid with two nested loops, r over the rows and c over the columns.
    5. Queue every rotten orange: if grid[r][c] === 2, push [r, c]. All of them spread the rot in the first minute.
    6. Count every fresh orange: if grid[r][c] === 1, do fresh++. Empty cells are neither.
    7. Define directions, the four moves as [row change, column change] pairs: up, down, left and right. Each changes the row or the column by one, never both, so diagonals are excluded.
    8. Declare minutes at 0. When no orange is fresh at the start, no time passes.
    9. Declare head at 0: the index of the next orange to take from queue.
    10. Write the outer while loop that runs while head < queue.length and fresh > 0. Each turn is one minute. Without the fresh > 0 test, one extra minute would be counted after the last orange rots.
    11. Count the minute with minutes++.
    12. Remember where this minute's level ends: levelEnd is queue.length. The oranges from head up to levelEnd spread the rot in this minute, and the ones that rot now are added past levelEnd for the next minute.
    13. Write the inner while loop over head < levelEnd. One pass of it handles one level of the BFS.
    14. Take the next orange: destructure queue[head] into row and col, then do head++.
    15. Visit the four neighbours with for...of over directions, destructuring each move into dr and dc, and compute nextRow and nextCol.
    16. Check the neighbour is inside the grid and holds 1. The bounds come first: && stops at the first false part, so the grid is read only for a cell that exists.
    17. Rot it: set it to 2, do fresh--, and push [nextRow, nextCol] onto the queue. Marking it at once keeps any other orange from adding it a second time.
    18. After the loops, return -1 if fresh > 0, and minutes otherwise.

    The solution in JavaScript

    class Solution {
        orangesRotting(grid) {
            const rows = grid.length;
            const cols = grid[0].length;
            const queue = [];
            let fresh = 0;
            for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    if (grid[r][c] === 2) {
                        queue.push([r, c]);
                    }
                    if (grid[r][c] === 1) {
                        fresh++;
                    }
                }
            }
            const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
            let minutes = 0;
            let head = 0;
            while (head < queue.length && fresh > 0) {
                minutes++;
                const levelEnd = queue.length;
                while (head < levelEnd) {
                    const [row, col] = queue[head];
                    head++;
                    for (const [dr, dc] of directions) {
                        const nextRow = row + dr;
                        const nextCol = col + dc;
                        if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols && grid[nextRow][nextCol] === 1) {
                            grid[nextRow][nextCol] = 2;
                            fresh--;
                            queue.push([nextRow, nextCol]);
                        }
                    }
                }
            }
            if (fresh > 0) {
                return -1;
            }
            return minutes;
        }
    }
    

    The JavaScript details worth knowing:

    • Destructuring. const [row, col] = queue[head] unpacks a two-element array into two variables. for (const [dr, dc] of directions) does the same for each move.
    • Growing while reading. The queue grows during the loop, so queue.length changes. Copying it into levelEnd before the inner loop freezes the size of the current level.
    • The grid is changed in place. Arrays are passed by reference, so the method turns the caller's fresh oranges into 2s. After the first example, the grid is [[2,2,2],[2,2,0],[0,2,2]].
    • Short-circuit order. Reading grid[-1][0] would throw a TypeError, because grid[-1] is undefined. Putting the bounds tests before the grid read avoids that.
    • [[0]] and one-row grids. grid[0].length works for any grid that has at least one row, and the statement guarantees that.

    Complexity

    Time: O(m · n). The first scan visits each cell once. In the search, each cell joins the queue at most once and looks at its four neighbours, so the work is a constant per cell.

    Space: O(m · n). In the worst case every cell is an orange and passes through the queue. The head index never removes anything, so the queue keeps every pair until the method returns, which is still linear.

    Edge cases and common mistakes

    • No fresh orange at the start. [[0, 2]] and [[2]] both return 0, and [[0]] does too. The fresh > 0 test stops the loop before any minute is counted.
    • Fresh orange and no rotten one. [[1]] returns -1, because the queue is empty and fresh stays 1.
    • Unreachable oranges. In the second example the bottom-left orange stays fresh forever, so fresh is still above 0 when the queue runs out.
    • Counting an extra minute. Without fresh > 0 in the loop condition, the last rotten oranges would get one more turn, looking for neighbours that no longer exist, and the answer would be one too large.
    • Marking the orange too late. If the cell is set to 2 only when it is taken from the queue, two neighbours can both add it, and fresh goes down twice for the same orange.
    • Treating diagonals as neighbours. With eight neighbours, the second example returns 2 instead of -1, because the bottom-left orange rots from the diagonal.
    • Handling one rotten orange at a time. Running a separate search from each rotten orange measures the wrong thing: all of them rot their neighbours in the same minute, so they must share one queue.

    Testing

    • [[2,1,1],[1,1,0],[0,1,1]] gives 4.
    • [[2,1,1],[0,1,1],[1,0,1]] gives -1.
    • [[0,2]] gives 0.
    • [[1]] gives -1.
    • [[1,2]] gives 1.
    • [[2,1,1,1,1]] gives 4: one cell per minute along the row.
    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free