994. Rotting Oranges

練習日期:2026-01-01

難度:Medium

類型:Array、Breadth-First Search、Matrix

📘 題目敘述

給定一個 m x n 的網格 grid,其中:

  • 0 代表空格
  • 1 代表新鮮橘子
  • 2 代表腐爛橘子

每過 1 分鐘,任何與腐爛橘子「上下左右相鄰」的新鮮橘子都會變腐爛。

請回傳讓所有新鮮橘子都腐爛所需要的最少分鐘數。如果不可能讓所有新鮮橘子都腐爛,回傳 -1

條件限制

  • m == grid.length
  • n == grid[i].length
  • 1 ≤ m, n ≤ 10
  • grid[i][j] 只會是 012

🧠 解題思路(一)用vector

我的想法是用 BFS 的概念:
把「目前腐爛的橘子」當成同一層,一分鐘內把它們四周的新鮮橘子全部感染。
感染完一輪就代表過了 1 分鐘,然後再處理下一輪新感染的橘子。


所有變數

  • m, n:網格大小
  • fresh:目前還剩多少顆新鮮橘子(grid == 1 的數量)
  • rotten:用兩個 vector 存所有腐爛橘子的座標
    • rotten[0] 存 x(列)
    • rotten[1] 存 y(行)
  • orot:上一輪(上一分鐘)腐爛橘子處理到的位置(index 起點)
  • nrot:目前已知腐爛橘子的總數(index 終點)
  • dx, dy:四方向移動(下、上、右、左)
  • min:已經過了幾分鐘

(我用 orot ~ nrot-1 這段 index 來表示「這一分鐘要擴散的腐爛橘子集合」)


步驟一:先掃一次 grid,統計初始狀態

我先把所有格子掃一遍:

  • 遇到 1fresh++(記錄新鮮橘子數量)
  • 遇到 2:把座標 (i, j) 存進 rotten,並且 nrot++

這樣做完後:

  • fresh 知道還剩多少顆要感染
  • rotten 裡面存著「一開始就腐爛」的所有橘子
  • nrot 就是初始腐爛橘子的數量

步驟二:用 while 迴圈模擬「一分鐘一層的擴散」

只要 fresh != 0(還有新鮮橘子),就繼續跑:

這一分鐘我會處理哪些腐爛橘子?

我只處理 index 在 [orot, nrot) 的那些腐爛橘子:

  • orot 是上一輪處理的結尾
  • nrot 是目前腐爛清單的結尾
  • 所以 [orot, nrot) 就是「這一分鐘要擴散的腐爛橘子」

這樣做的效果等同於 BFS 的分層。


情況一:擴散(感染四周)

對每一顆「這一分鐘要擴散」的腐爛橘子 (x, y)

  • 我看它的四個方向 (xn, yn)
  • 如果 (xn, yn) 在範圍內,且 grid[xn][yn] == 1
  • 代表是一顆新鮮橘子會被感染
  • (xn, yn) 加到 rotten(變成新的腐爛橘子)
  • fresh--(新鮮橘子減少)
  • grid[xn][yn] = 2(標記成腐爛,避免重複感染)

情況二:這一輪沒有新增腐爛,但 fresh 還沒變 0 → 不可能完成

感染完一輪後,我會檢查:

  • 如果 nrot == rotten[0].size()fresh != 0

意思是:

  • 這一輪擴散完之後,腐爛橘子數量完全沒變(沒有新增)
  • 但還有新鮮橘子存在

→ 表示剩下的新鮮橘子被隔開了,永遠感染不到,所以直接回傳 -1


更新到下一分鐘

如果還能繼續:

  • orot = nrot(下一輪從這裡開始處理)
  • nrot = rotten[0].size()(更新目前腐爛總數)
  • min++(過了 1 分鐘)

最後回傳

fresh == 0 時,代表全部都感染完了,回傳 min。(若一開始 fresh == 0,while 不會進去,直接回傳 0)

💻 程式碼實作(一)

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int fresh = 0;
        int orot = 0, nrot = 0;
        int dx[4] = {1, -1, 0, 0};
        int dy[4] = {0, 0, 1, -1};
        int min = 0;
        int xn = 0, yn = 0;
        vector<vector<int>> rotten(2);

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    fresh = fresh + 1;
                } else if (grid[i][j] == 2) {
                    nrot = nrot + 1;
                    rotten[0].push_back(i);
                    rotten[1].push_back(j);
                }
            }
        }

        while (fresh != 0) {
            for (int k = orot; k < nrot; k++) {
                int x = rotten[0][k];
                int y = rotten[1][k];
                for (int l = 0; l < 4; l++) {
                    xn = x + dx[l];
                    yn = y + dy[l];
                    if (xn >= 0 && xn < m && yn >= 0 && yn < n) {
                        if (grid[xn][yn] == 1) {
                            rotten[0].push_back(xn);
                            rotten[1].push_back(yn);
                            fresh = fresh - 1;
                            grid[xn][yn] = 2;
                        }
                    }
                }
            }

            if (nrot == rotten[0].size() && fresh != 0) {
                return -1;
            }
            orot = nrot;
            nrot = rotten[0].size();
            min = min + 1;
        }
        return min;
    }
};

🧠 解題思路(二)用queue

這題我用 BFS,因為「腐爛擴散」本質就是從所有腐爛點同時往外一層一層擴散,而且題目要的是「最少分鐘數」,也就是最短層數。

我做法的關鍵是「多源 BFS」:

  • 一開始把所有腐爛橘子(值為 2)的位置都丟進 queue,代表它們在第 0 分鐘同時開始擴散
  • 每處理完 queue 的一整層,就代表過了 1 分鐘
  • 在擴散時,遇到新鮮橘子(值為 1)就把它改成腐爛(設成 2),並加入 queue,等下一分鐘繼續擴散

另外我用 fresh 記錄還剩多少新鮮橘子,這樣 BFS 結束後只要看 fresh 是否歸零,就能判斷到底有沒有全部腐爛。

所有變數

  • count:分鐘數計數器。我用「每跑完一層就 +1」的方式,所以最後回傳的是擴散了幾層(幾分鐘)
  • fresh:目前還剩多少顆新鮮橘子(值為 1)
  • m:列數
  • n:行數
  • x:queue,存待處理格子的 row
  • y:queue,存待處理格子的 col
  • dxdy:四方向位移(下、上、右、左)

🪜 主要流程步驟

1. 掃描整張 grid,收集所有起始腐爛點 + 計算 fresh

我先走過整個矩陣:

  • 看到 2:把座標放進 queue(多源 BFS 的起點)
  • 看到 1fresh++

2. 如果 fresh 一開始就是 0,直接回傳 0

代表根本沒有新鮮橘子要被感染,所以答案就是 0 分鐘。


3. BFS 分層擴散,每一層代表過了 1 分鐘

只要 queue 還不空,就代表目前還有腐爛橘子會繼續往外感染。

我每次先用 s = x.size() 記錄這一層有幾個點,然後 count++,表示「接下來要處理的是下一分鐘會腐爛的擴散」。


4. 對每個腐爛點,往四個方向感染新鮮橘子

對四方向鄰居:

  • 如果出界:略過
  • 如果是新鮮橘子 grid[nx][ny] == 1
  • 把它改成 2(代表它已經腐爛)
  • 丟進 queue(下一分鐘它也能繼續感染別人)
  • fresh--(新鮮橘子數量減少)

5. BFS 結束後,用 fresh 判斷答案

  • 如果 fresh > 0:代表有些新鮮橘子永遠感染不到,回傳 -1
  • 否則回傳 count

⏱️ 複雜度

  • 時間複雜度:O(mn),每個格子最多被放進 queue 一次、處理一次
  • 空間複雜度:O(mn),最壞情況 queue 可能存很多格子

💻 程式碼實作(二)

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int count = -1, fresh = 0;
        int m = grid.size(), n = grid[0].size();
        queue<int> x, y;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 2) {
                    x.push(i);
                    y.push(j);
                } else if (grid[i][j] == 1) {
                    fresh++;
                }
            }
        }
        if (fresh == 0) {
            return 0;
        }
        int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
        while (!x.empty()) {
            int s = x.size();
            count++;
            for (int k = 0; k < s; k++) {
                for (int i = 0; i < 4; i++) {
                    int nx = x.front() + dx[i], ny = y.front() + dy[i];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n) {
                        continue;
                    } else if (grid[nx][ny] == 1) {
                        grid[nx][ny] = 2;
                        x.push(nx);
                        y.push(ny);
                        fresh--;
                    }
                }
                x.pop();
                y.pop();
            }
        }
        if (fresh > 0) {
            return -1;
        }
        return count;
    }
};

https://leetcode.com/problems/rotting-oranges/

作者: scottnick
撰寫日期: 2026-01-01
類別:
原文連結: https://scottnick.github.io/cpp-notes/all%20problems/994-rotting-oranges.html