文章目录
题目
标题和出处
标题:腐烂的橘子
出处:994. 腐烂的橘子
难度
4 级
题目描述
要求
给定一个 m × n \texttt{m} \times \texttt{n} m×n 的网格 grid \texttt{grid} grid,其中每个单元格可以有以下三个值之一:
- 值 0 \texttt{0} 0 代表空单元格;
- 值 1 \texttt{1} 1 代表新鲜橘子;
- 值 2 \texttt{2} 2 代表腐烂的橘子。
每分钟,腐烂的橘子周围 4 \texttt{4} 4 个方向上相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 \texttt{-1} -1。
示例
示例 1:

输入: grid = [[2,1,1],[1,1,0],[0,1,1]] \texttt{grid = [[2,1,1],[1,1,0],[0,1,1]]} grid = [[2,1,1],[1,1,0],[0,1,1]]
输出: 4 \texttt{4} 4
示例 2:
输入: grid = [[2,1,1],[0,1,1],[1,0,1]] \texttt{grid = [[2,1,1],[0,1,1],[1,0,1]]} grid = [[2,1,1],[0,1,1],[1,0,1]]
输出: -1 \texttt{-1} -1
解释:左下角的橘子(第 2 \texttt{2} 2 行,第 0 \texttt{0} 0 列)永远不会腐烂,因为腐烂只会发生在 4 \texttt{4} 4 个方向上。
示例 3:
输入: grid = [[0,2]] \texttt{grid = [[0,2]]} grid = [[0,2]]
输出: 0 \texttt{0} 0
解释:因为 0 \texttt{0} 0 分钟时已经没有新鲜橘子了,所以答案就是 0 \texttt{0} 0。
数据范围
- m = grid.length \texttt{m} = \texttt{grid.length} m=grid.length
- n = grid[i].length \texttt{n} = \texttt{grid[i].length} n=grid[i].length
- 1 ≤ m, n ≤ 10 \texttt{1} \le \texttt{m, n} \le \texttt{10} 1≤m, n≤10
- grid[i][j] \texttt{grid[i][j]} grid[i][j] 为 0 \texttt{0} 0、 1 \texttt{1} 1 或 2 \texttt{2} 2
解法
思路和算法
只有当一个新鲜橘子和至少一个腐烂的橘子相连时,该新鲜橘子才会腐烂。如果一个新鲜橘子和多个腐烂的橘子相连,则该新鲜橘子腐烂的时间取决于距离该新鲜橘子最近的腐烂橘子,这里的距离不是曼哈顿距离,而是经过的橘子数。
首先遍历网格,获得新鲜橘子的数量。如果新鲜橘子的数量是 0 0 0,则 0 0 0 分钟时已经没有新鲜橘子,返回 0 0 0。
由于广度优先搜索的遍历顺序是和起始点的距离递增的顺序,因此可以使用广度优先搜索计算每个橘子的腐烂时间以及所有新鲜橘子都腐烂的时间,做法是从所有腐烂的橘子出发执行多源广度优先搜索,搜索过程中记录时间。
广度优先搜索需要使用与网格相同大小的二维数组记录每个元素是否被访问过,初始时只有腐烂的橘子的状态是已访问,其余元素的状态都是未访问。由于需要计算每个橘子的腐烂时间,因此在广度优先搜索的过程中需要将元素分层,确保每一轮遍历的元素为同一层的全部待访问元素,同一层的全部待访问元素为同一分钟腐烂的全部橘子。
初始时,将所有腐烂的橘子入队列。每一轮遍历之前需要首先得到队列内的元素个数,此时队列内的元素为同一层的全部待访问元素,然后访问这些元素,并将与这些元素相邻且未访问的新鲜橘子入队列。一轮遍历结束之后,当前层的全部元素都已经出队列并被访问,此时队列内的元素为下一层的全部待访问元素,下一轮遍历时即可访问下一层的全部待访问元素。该做法可以确保每一轮遍历的元素为同一层的全部待访问元素。
具体做法是,将分钟数初始化为 − 1 -1 −1,表示尚未访问任何元素。每一轮遍历时,将分钟数加 1 1 1,然后遍历当前层的全部待访问元素,对于当前层的每个待访问元素,如果存在相邻元素是未访问的新鲜橘子,执行如下操作。
-
将新鲜橘子的数量减 1 1 1,表示该新鲜橘子将在下一分钟腐烂。
-
将新鲜橘子对应元素的状态设为已访问。
-
将新鲜橘子入队列。
当队列为空时,遍历结束,此时如果新鲜橘子的数量是 0 0 0,则没有新鲜橘子,返回分钟数,否则返回 − 1 -1 −1。
实现方面有以下两点说明。
-
对于每个元素需要向四个方向遍历,可以创建方向数组实现四个方向的遍历。
-
此处的解法为新建与网格相同大小的二维数组记录每个元素是否被访问过,也可以不新建二维数组,而是在网格上原地修改访问过的元素。虽然原地修改可以省略新建二维数组的空间,但是不能省略队列空间,因此空间复杂度相同,而且原地修改会改变网格的元素,使得计算橘子腐烂的时间之后无法再次使用网格信息。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
static final int EMPTY = 0, FRESH = 1, ROTTEN = 2;
static final int INFINITY = Integer.MAX_VALUE / 2;
public int orangesRotting(int[][] grid) {
int freshCount = 0;
int m = grid.length, n = grid[0].length;
boolean[][] visited = new boolean[m][n];
Queue<int[]> queue = new ArrayDeque<int[]>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == FRESH) {
freshCount++;
} else if (grid[i][j] == ROTTEN) {
visited[i][j] = true;
queue.offer(new int[]{i, j});
}
}
}
if (freshCount == 0) {
return 0;
}
int minutes = -1;
while (!queue.isEmpty()) {
minutes++;
int size = queue.size();
for (int i = 0; i < size; i++) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && grid[newRow][newCol] == FRESH && !visited[newRow][newCol]) {
freshCount--;
visited[newRow][newCol] = true;
queue.offer(new int[]{newRow, newCol});
}
}
}
}
return freshCount == 0 ? minutes : -1;
}
}
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是网格 grid \textit{grid} grid 的行数和列数。计算新鲜橘子数量和获得腐烂的橘子需要遍历网格一次,广度优先搜索最多需要访问每个元素一次。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是网格 grid \textit{grid} grid 的行数和列数。队列需要 O ( m n ) O(mn) O(mn) 的空间。