一、题目描述
现有一个机器人,可放置于 M × N 的网格中任意位置,每个网格包含一个非负整数编号,当相邻网格的数字编号差值的绝对值小于等于 1 时,机器人可以在网格间移动。
问题: 求机器人可活动的最大范围对应的网格点数目。
说明:网格左上角坐标为 (0,0) ,右下角坐标为(m−1,n−1) ,机器人只能在相邻网格间上下左右移动。
二、输入输出描述
1. 输入描述
- 第一行:两个整数 M 和 N ,M 表示网格的行数 N 表示网格的列数,1 ≤ M,N ≤ 150;
- 后续 M 行:每行 N 个数值(数值大小用 k 表示, 0 ≤ k ≤ 50),数值间用单个空格分隔。
2. 输出描述
- 输出一个整数,表示机器人可活动的最大区域的网格点数目。
三、示例
|----|-------------------------------------|
| 输入 | 4 4 1 2 5 2 2 4 4 5 3 5 7 1 4 6 2 4 |
| 输出 | 6 |
| 说明 | |
四、解题思路
1. 核心思想
通过广度优先搜索(BFS) 遍历矩阵中所有未访问的单元格,逐个计算其所在连通区域的大小,最终筛选出最大的区域大小 ------ 核心是 "遍历不重复 + 连通性判定 + 大小统计"。
2. 问题本质分析
问题本质是带条件的连通区域查找与大小统计:
- 连通性规则:单元格仅与 "上下左右相邻 + 数值差值≤1" 的单元格连通(非传统的 "数值相同" 连通,而是差值范围放宽到≤1)。
- 目标:找到所有满足该连通规则的区域中,包含单元格数量最多的那个,输出其数量。
- 关键约束:需避免重复遍历(同一单元格只能属于一个连通区域),因此需要访问标记数组。
3. 核心逻辑
- 遍历全覆盖:逐行逐列遍历矩阵所有单元格,确保每个未访问的单元格都能触发一次 BFS,覆盖所有连通区域。
- BFS 连通性判定:以队列作为核心数据结构,逐层扩展相邻单元格,仅纳入满足 "范围 + 访问 + 差值" 条件的单元格,统计区域大小。
- 最大值更新:每次 BFS 计算出一个区域的大小后,与当前最大值对比,保留更大值。
4. 步骤拆解
-
输入读取与初始化:
- 读取矩阵尺寸
m、n和矩阵grid。 - 初始化访问标记数组
visited(全为false)、最大区域大小maxSize(初始 0)、方向偏移量dirs。
- 读取矩阵尺寸
-
遍历矩阵,触发 BFS:
- 循环遍历矩阵的每个单元格
(i,j):- 若
visited[i][j] == false(未被访问),调用 BFS 计算该单元格所在连通区域的大小。 - 用 BFS 返回的区域大小更新
maxSize(取最大值)。
- 若
- 循环遍历矩阵的每个单元格
-
BFS 计算连通区域大小:
- 初始化队列,加入起始单元格
(x,y),标记为已访问,区域大小初始化为 1。 - 队列非空时,弹出头部单元格,遍历其四个方向的相邻单元格:
- 筛选符合条件的相邻单元格(范围合法 + 未访问 + 数值差值≤1)。
- 满足条件则标记访问、加入队列、区域大小 + 1。
- 队列遍历完成后,返回当前区域大小。
- 初始化队列,加入起始单元格
-
结果输出:
- 遍历完所有单元格后,返回并输出
maxSize(最大连通区域的大小)。
- 遍历完所有单元格后,返回并输出
五、代码实现
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取矩阵的行数和列数
int m = sc.nextInt();
int n = sc.nextInt();
// 读取矩阵内容
int[][] grid = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
grid[i][j] = sc.nextInt();
}
}
// 输出最大区域的大小
System.out.println(getResult(grid, m, n));
}
// 计算矩阵中最大的连通区域的大小
public static int getResult(int[][] grid, int m, int n) {
boolean[][] visited = new boolean[m][n]; // 访问标记数组
int maxSize = 0; // 记录最大的区域大小
// 四个方向的偏移量,上下左右
int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// 遍历整个矩阵
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 如果当前点没有被访问过,执行 BFS 计算该区域的大小
if (!visited[i][j]) {
int size = bfs(grid, visited, i, j, m, n, dirs);
// 更新最大区域的大小
maxSize = Math.max(maxSize, size);
}
}
}
return maxSize; // 返回最大的区域大小
}
// 使用 BFS 查找从 (x, y) 出发的连通区域的大小
private static int bfs(int[][] grid, boolean[][] visited, int x, int y, int m, int n, int[][] dirs) {
Queue<int[]> queue = new LinkedList<>(); // BFS 使用的队列
queue.offer(new int[]{x, y}); // 将起始点加入队列
visited[x][y] = true; // 标记起始点为已访问
int size = 1; // 当前区域的大小,从起始点开始
// 当队列不为空时,继续遍历
while (!queue.isEmpty()) {
int[] point = queue.poll(); // 弹出队列中的一个元素
// 遍历当前点的四个方向
for (int[] dir : dirs) {
int newX = point[0] + dir[0]; // 计算新点的横坐标
int newY = point[1] + dir[1]; // 计算新点的纵坐标
// 确保新点在矩阵范围内,并且未被访问过
// 同时新点与当前点的差值小于等于 1,才将新点加入队列
if (newX >= 0 && newX < m && newY >= 0 && newY < n && !visited[newX][newY]
&& Math.abs(grid[point[0]][point[1]] - grid[newX][newY]) <= 1) {
visited[newX][newY] = true; // 标记新点为已访问
queue.offer(new int[]{newX, newY}); // 将新点加入队列
size++; // 增加当前区域的大小
}
}
}
return size; // 返回当前区域的大小
}
}