题目背景
题目如图
题目描述了一个二维数组,代表了一个魔幻世界的格子,每个格子都有一个危险程度。通过给定一个能力值 X,决定哪些格子是安全的。如果格子的危险程度小于或等于 X,那么这个格子是安全的。我们的目标是计算在这些安全格子中,哪些可以通过上下左右相连组成一个安全区。
安全区的定义
安全区是由若干个相邻的安全格子构成的区域。相邻格子可以是上下、左右相连。即:若格子 (i, j) 是安全的,且其相邻格子 (i-1, j)、(i+1, j)、(i, j-1) 或 (i, j+1) 也是安全的,那么这些格子组成一个连通区域(安全区)。我们需要计算出所有这样的安全区的数量。
解决方案:深度优先搜索(DFS)
1. 如何判断一个格子是否安全
首先,我们需要判断每个格子是否是安全的。这可以通过比较该格子的危险程度与给定的能力值 X 来实现:
- 如果格子的危险程度
a[i][j] <= X,则该格子是安全的; - 如果格子的危险程度
a[i][j] > X,则该格子不安全。
2. 如何确定安全区
安全区由多个相邻的安全格子组成,邻接的安全格子可以通过上下左右四个方向相连。为了遍历所有相邻的安全格子,我们可以使用深度优先搜索(DFS) 。
- DFS 是一种递归的图遍历方法。对于每个安全格子,我们会递归地访问它所有相邻的安全格子,并且标记已经访问过的格子,避免重复计算。
- 每次启动 DFS 时,我们都认为发现了一个新的安全区。
3. 如何实现 DFS
我们会从一个未访问的安全格子开始,访问它的上下左右相邻的格子,直到无法继续为止。对于每一个新发现的安全区,我们都会增加计数器。
DFS 具体实现步骤:
- 遍历所有格子:逐行逐列遍历二维数组,检查每个格子是否是安全的且没有被访问过。
- 启动 DFS:如果当前格子是安全的且未被访问过,则启动 DFS,标记当前安全区中的所有安全格子为已访问。
- 递归搜索:在 DFS 中,对于当前格子,我们递归地搜索它的四个邻居(上下左右),并将所有相邻的安全格子标记为已访问。
- 计数安全区:每当我们启动一次 DFS,就表示找到了一个新的安全区,计数器加 1。
代码实现
public class Main {
// 定义四个方向:上下左右
private static final int[] dx = {-1, 1, 0, 0};
private static final int[] dy = {0, 0, -1, 1};
public static int solution(int n, int m, int X, int[][] a) {
// 创建一个访问标记数组
boolean[][] visited = new boolean[n][m];
int safeZones = 0;
// 深度优先搜索 (DFS)
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 如果当前格子是安全的且尚未被访问过
if (a[i][j] <= X && !visited[i][j]) {
// 启动 DFS,标记整个安全区
dfs(i, j, n, m, a, visited, X);
// 每发现一个新的安全区,计数器加1
safeZones++;
}
}
}
return safeZones;
}
// DFS 搜索相邻的安全格子
private static void dfs(int i, int j, int n, int m, int[][] a, boolean[][] visited, int X) {
// 边界检查
if (i < 0 || i >= n || j < 0 || j >= m) return;
// 如果该格子不安全或已经被访问过,直接返回
if (a[i][j] > X || visited[i][j]) return;
// 标记当前格子为已访问
visited[i][j] = true;
// 递归搜索上下左右四个相邻格子
for (int d = 0; d < 4; d++) {
int ni = i + dx[d];
int nj = j + dy[d];
dfs(ni, nj, n, m, a, visited, X);
}
}
public static void main(String[] args) {
int[][] a1 = {{2, 3, 3}, {3, 3, 3}, {3, 3, 3}};
int[][] a2 = {{6, 6}, {6, 4}};
int[][] a3 = {{1, 2, 2}, {2, 3, 3}, {3, 4, 5}};
System.out.println(solution(3, 3, 4, a1) == 1); // 输出 1
System.out.println(solution(2, 2, 5, a2) == 1); // 输出 1
System.out.println(solution(3, 3, 3, a3) == 1); // 输出 1
}
}
代码解析
-
dx, dy 数组:这两个数组用于表示四个方向的偏移量。
dx数组表示行的变化,dy数组表示列的变化。- 上方:
dx = -1, dy = 0 - 下方:
dx = 1, dy = 0 - 左方:
dx = 0, dy = -1 - 右方:
dx = 0, dy = 1
- 上方:
-
visited[][] 数组:这个数组用来标记每个格子是否已经被访问过。如果格子已经被访问过,DFS 就不会再访问它,防止重复遍历。
-
dfs 方法:这是一个递归函数,用来从一个格子开始深度优先遍历,访问所有与当前格子相连的安全格子。
if (i < 0 || i >= n || j < 0 || j >= m):检查当前格子是否越界。if (a[i][j] > X || visited[i][j]):检查当前格子是否安全且未被访问过。visited[i][j] = true;:标记当前格子已访问。- 使用四个方向递归搜索相邻的安全格子。
-
主函数中的循环:
- 外层循环遍历所有的格子。
- 对每个格子,检查是否是安全的且未被访问过。如果是,就调用 DFS,标记所有相邻的安全格子为已访问,并计数一个新的安全区。
复杂度分析
-
时间复杂度:
- 每个格子最多被访问一次,因此时间复杂度是
O(n * m),其中n是行数,m是列数。
- 每个格子最多被访问一次,因此时间复杂度是
-
空间复杂度:
- 使用了
visited[][]数组来存储每个格子的访问状态,因此空间复杂度是O(n * m)。
- 使用了
总结
通过这道题,我们可以深入理解如何在一个二维数组中使用深度优先搜索来解决连通区域的问题。通过合适的标记和递归遍历,我们能够有效地计算出安全区的数量。