Rotting Oranges

題目描述

給定一個 m×n 的網格,0 是空格,1 是新鮮橘子,2 是腐爛橘子。每分鐘,腐爛橘子的四鄰新鮮橘子會變腐爛。回傳所有橘子變爛的最少分鐘數,若不可能回傳 -1。

輸入: grid = [[2,1,1],[1,1,0],[0,1,1]]
輸出: 4

輸入: grid = [[2,1,1],[0,1,1],[1,0,1]]
輸出: -1
解釋: 左下角的橘子永遠不會爛

解題思路

1

收集所有爛橘子

掃描網格,把所有 2 的位置加入 Queue,同時計數新鮮橘子

2

多源 BFS

所有爛橘子同時開始擴散

3

每一輪 = 一分鐘

處理 Queue 中當前層的所有爛橘子

4

感染鄰居

四方向的新鮮橘子變爛,加入 Queue

5

檢查結果

新鮮橘子數量 == 0?是→回傳分鐘數,否→-1

程式碼

C#

csharp
public class Solution {
    public int OrangesRotting(int[][] grid) {
        int m = grid.Length, n = grid[0].Length;
        var queue = new Queue<(int r, int c)>();
        int fresh = 0;
 
        // 收集所有爛橘子,計數新鮮橘子
        for (int r = 0; r < m; r++) {
            for (int c = 0; c < n; c++) {
                if (grid[r][c] == 2) queue.Enqueue((r, c));
                else if (grid[r][c] == 1) fresh++;
            }
        }
 
        if (fresh == 0) return 0;
 
        int[][] dirs = { new[]{0,1}, new[]{0,-1}, new[]{1,0}, new[]{-1,0} };
        int minutes = 0;
 
        while (queue.Count > 0 && fresh > 0) {
            minutes++;
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                var (r, c) = queue.Dequeue();
                foreach (var d in dirs) {
                    int nr = r + d[0], nc = c + d[1];
                    if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 1) {
                        grid[nr][nc] = 2;
                        fresh--;
                        queue.Enqueue((nr, nc));
                    }
                }
            }
        }
        return fresh == 0 ? minutes : -1;
    }
}

TypeScript

typescript
function orangesRotting(grid: number[][]): number {
    const m = grid.length, n = grid[0].length;
    const queue: [number, number][] = [];
    let fresh = 0;
 
    for (let r = 0; r < m; r++) {
        for (let c = 0; c < n; c++) {
            if (grid[r][c] === 2) queue.push([r, c]);
            else if (grid[r][c] === 1) fresh++;
        }
    }
 
    if (fresh === 0) return 0;
 
    const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
    let minutes = 0;
 
    while (queue.length > 0 && fresh > 0) {
        minutes++;
        const size = queue.length;
        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 < m && nc >= 0 && nc < n && grid[nr][nc] === 1) {
                    grid[nr][nc] = 2;
                    fresh--;
                    queue.push([nr, nc]);
                }
            }
        }
    }
    return fresh === 0 ? minutes : -1;
}

Python

python
from collections import deque
 
def orangesRotting(grid: list[list[int]]) -> int:
    m, n = len(grid), len(grid[0])
    queue = deque()
    fresh = 0
 
    for r in range(m):
        for c in range(n):
            if grid[r][c] == 2:
                queue.append((r, c))
            elif grid[r][c] == 1:
                fresh += 1
 
    if fresh == 0:
        return 0
 
    dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    minutes = 0
 
    while queue and fresh > 0:
        minutes += 1
        for _ in range(len(queue)):
            r, c = queue.popleft()
            for dr, dc in dirs:
                nr, nc = r + dr, c + dc
                if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 1:
                    grid[nr][nc] = 2
                    fresh -= 1
                    queue.append((nr, nc))
 
    return minutes if fresh == 0 else -1

複雜度分析

| | Time | Space | |--|------|-------| | 本解法 | O(m × n) | O(m × n) |

圖解

t=0: 爛[0,0]
1 分鐘
t=1: [0,1],[1,0] 爛
1 分鐘
t=2: [0,2],[1,1] 爛
1 分鐘
t=3: [2,1] 爛
1 分鐘
t=4: [2,2] 爛

進階觀點

💡面試追問

多源 BFS vs 單源 BFS:單源 BFS 從一個起點擴散,多源 BFS 從所有起點同時擴散。概念上等於加了一個虛擬超級源點連接所有真正的起點。

邊界條件:沒有新鮮橘子時直接回傳 0(不是 -1)。這是常見的 edge case 陷阱。

變體題:LC 286 Walls and Gates(多源 BFS 求距離)、LC 1162 As Far from Land as Possible。

理解測驗

🤔 為什麼所有爛橘子要同時放入 Queue,而不是一個一個處理?

🤔 如果一開始就沒有新鮮橘子,答案是什麼?

你可能也想看

1926. Nearest Exit from Entrance in Maze215. Kth Largest Element in an Array

按 ← → 鍵切換課程