1.题目描述

题目解答
这道题又涉及到了一个全新的知识点:图的深度优先搜索。
深度优先搜索的定义可以理解为:优先不停的向下层寻找,直到碰壁了之后再返回上层,之后如果可以的话就继续向下层搜索。正好实现这种搜索方式的方法就是递归。
所以这道题我们可以先以左上角的1为中心,当遇见了一个全新的1,就说明发现了一个全新的岛屿,将这个1变为已标记,计数器加一。之后对这个岛屿进行深度优先搜索,上下左右的1全部变为已标记,就说明是这个岛屿的一部分,直到边界条件终止。
之后从头开始一行一行进行遍历,如果沿途中发现了未被标记的1,就说明又有新大陆,如法炮制即可。
java
class Solution {
// 岛屿计数:每发现一块没访问过的陆地就 +1,再用 DFS 把整座岛"淹没"掉
int count = 0;
public int numIslands(char[][] grid) {
// visited[i][j] = true 表示这个格子已经被访问过(属于某座已知岛屿)
boolean[][] visited = new boolean[grid.length][grid[0].length];
// 主循环:扫遍网格里每一个格子
// 这里外层循环 i 用的是 grid[0].length(列数),内层 j 用的是 grid.length(行数),
// 配合 grid[j][i] 的访问方式,相当于 j 当行号、i 当列号
for (int i = 0; i < grid[0].length; i++) {
for (int j = 0; j < grid.length; j++) {
// 是海水、或者已经被访问过 → 跳过
if (grid[j][i] == '0' || visited[j][i]) {
continue;
}
// 到这里:遇到一块没访问过的陆地 = 发现一座新岛
visited[j][i] = true; // ★ 先把起点标记掉
count++; // 岛屿数 +1
dfs(grid, visited, i, j); // 用 DFS 把整座岛"淹没"(标记掉所有相连的陆地)
}
}
return count;
}
/**
* DFS 淹没函数:从坐标 (x, y) 出发(这里 x 当列、y 当行),
* 把与它四连通的所有陆地标记为已访问
*/
public void dfs(char[][] grid, boolean[][] visited, int x, int y) {
// 4 个方向:右、下、左、上(用数组比写 4 个 if-else 干净)
int[][] dir = {{0,1}, {1,0}, {-1,0}, {0,-1}};
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0]; // 候选邻居的列号
int nexty = y + dir[i][1]; // 候选邻居的行号
// ★ 越界时用 continue,不是 return!
// return 会让整个 DFS 直接结束,剩下的方向全部被放弃,整座岛会被"切散"
if (nextx > grid[0].length - 1 || nexty > grid.length - 1
|| nextx < 0 || nexty < 0) {
continue;
}
// 是陆地 + 没访问过 → 标记后继续递归
if (grid[nexty][nextx] == '1' && !visited[nexty][nextx]) {
visited[nexty][nextx] = true; // ★ 入栈前先标记,避免重复访问
dfs(grid, visited, nextx, nexty); // 递归,往邻居继续"淹"
} else {
// 是水、或者已经访问过 → 换下一个方向
continue;
}
}
}
}
2.题目描述

题目解答
这道题和上一道题是一种不同的解法,用到了广度优先搜索。
广度优先搜索可以理解为:尽量把当前所在层数的左右元素全部都遍历一遍,确认无误之后再进入下一层。
对应到这道题就是:先建立一个队列,然后由上至下扫描每一个格子,如果发现了格子中有腐烂的橘子,就将其入队。
然后对队列进行操作:记录当前队列长度的快照,之后依次将腐烂的句子上下左右的橘子进行感染。之后将原来的感染橘子出队,再将新感染的入队,直到队列为空或者新鲜橘子的数量为0终止循环。
java
class Solution {
public int orangesRotting(int[][] grid) {
// 局部 record:只在这个方法里可见的坐标二元组
// 访问器是方法:cur.x()、cur.y(),别忘了括号
record P(int x, int y) {}
// 4 个方向:右、下、左、上(列偏移, 行偏移)
int[][] dir = {{0,1}, {1,0}, {0,-1}, {-1,0}};
// BFS 队列:装的是"已腐烂、等待扩散"的橘子坐标
Deque<P> queue = new ArrayDeque<>();
int fresh = 0; // 新鲜橘子计数,用来判断"是否全部烂光"
int time = 0; // 经过的分钟数
// ===== 第一步:扫描全图,统计新鲜橘子 + 把腐烂橘子全部入队 =====
// 注意:外层 i 遍历列(grid[0].length),内层 j 遍历行(grid.length),
// 配合 grid[j][i] 的访问方式,相当于 j 当行号、i 当列号
for (int i = 0; i < grid[0].length; i++) {
for (int j = 0; j < grid.length; j++) {
if (grid[j][i] == 1) {
fresh++; // 是新鲜橘子,计数
}
if (grid[j][i] == 2) {
queue.offer(new P(i, j)); // 是腐烂橘子,入队当 BFS 起点
}
}
}
// ===== 第二步:按层 BFS,一圈圈向外扩散 =====
// 两个条件同时满足才继续:
// 1. 队列不空(还有橘子在等待扩散)
// 2. fresh > 0(还有新鲜橘子没烂)
// fresh == 0 时提前退出,避免 time 多加一轮
while (!queue.isEmpty() && fresh > 0) {
int size = queue.size(); // ★ 先抓快照:当前这一"层"有几个橘子要扩散
for (int i = 0; i < size; i++) { // 处理这一层的所有橘子
P cur = queue.poll(); // 取出队头
for (int k = 0; k < 4; k++) { // 4 个方向逐个试
int nextx = cur.x() + dir[k][0]; // 候选格子的列号(用 k!不是 i)
int nexty = cur.y() + dir[k][1]; // 候选格子的行号
// 越界、空格子(0)、已腐烂(2) → 这个方向走不通,换下一个
// ★ 注意 grid[0].length 是列上限、grid.length 是行上限
if (nextx < 0 || nextx > grid[0].length - 1
|| nexty < 0 || nexty > grid.length - 1
|| grid[nexty][nextx] == 0
|| grid[nexty][nextx] == 2) {
continue;
}
// 到这里:是新鲜橘子(1) → 被感染,变腐烂
grid[nexty][nextx] = 2; // ★ 染色的同时就标记,天然防止重复入队
queue.offer(new P(nextx, nexty)); // 加入下一轮扩散
fresh--; // 新鲜橘子少一个
}
}
time++; // ★ 这一层全部扩散完,才算过去 1 分钟
}
// ===== 第三步:判断结果 =====
// fresh > 0 说明有橘子永远烂不到(被空格围死),题目要求返回 -1
if (fresh > 0) {
return -1;
}
return time; // 全部烂光,返回总分钟数
}
}