题目来源:🔒LeetCode317:离建筑物最近的距离
问题抽象: 给定一个二维网格 grid(包含空地、建筑物和障碍物),要求找到一个 空地块 位置,使其到达所有建筑物的 曼哈顿距离总和最小(若存在解),满足以下核心需求:
-
网格元素定义:
0表示空地(可通行);1表示建筑物(目标点,不可通行);2表示障碍物(不可通行)。
-
距离规则:
- 使用 曼哈顿距离(水平+垂直方向步数,忽略对角线);
- 总距离 = 该空地到所有建筑物的曼哈顿距离之和。
-
输出要求:
- 返回最小总距离(整数);
- 若不存在满足条件的空地(如某建筑物无法被访问),返回
-1。
-
计算约束:
- 网格尺寸
m×n(1 ≤ m, n ≤ 100,建筑物数量≥1); - 时间复杂度 O(m²n²)(需优化,避免暴力枚举的空地效率);
- 空间复杂度 O(mn)(存储距离累计值)。
- 网格尺寸
-
边界处理:
- 无空地:返回
-1; - 建筑物不可达:若某建筑物被障碍物隔离(无空地可达),返回
-1; - 多解情况:仅需最小总距离值(无需输出位置);
- 单建筑物:总距离即为该空地到唯一建筑的距离。
- 无空地:返回
输入格式:二维整数数组 grid(行优先)
输出:最小总距离(整数)或 -1(无解)。
解题思路
核心:逆向BFS + 动态标记空地
-
逆向BFS:
- 从每个建筑物(
1)出发进行BFS,计算其到所有空地的距离,并累加到一个总和数组totalDist中。 - 避免从每个空地出发BFS(时间复杂度高),转而从建筑物出发,显著降低计算量。
- 从每个建筑物(
-
动态标记空地:
- 使用
mark变量标记当前建筑物能到达的空地。初始所有空地值为0,每遍历完一个建筑物,将其可达的空地值减1(变为-1、-2等)。 - 后续BFS仅处理与当前
mark值匹配的空地,确保只访问被之前所有建筑物访问过的空地。
- 使用
-
剪枝优化:
- 若某次BFS中无法找到与之前建筑物连通的有效空地(
res未更新),直接返回-1,无需继续计算。
- 若某次BFS中无法找到与之前建筑物连通的有效空地(
时间复杂度:
O
(
k
⋅
m
⋅
n
)
O(k \cdot m \cdot n)
O(k⋅m⋅n),其中
k
k
k 为建筑物数量,
m
×
n
m \times n
m×n 为网格大小。 空间复杂度:
O
(
m
⋅
n
)
O(m \cdot n)
O(m⋅n),存储 totalDist 和队列空间。
代码实现(Java版)🔥点击下载源码
class Solution {
private final int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int shortestDistance(int[][] grid) {
int rows = grid.length;
if (rows == 0) return -1;
int cols = grid[0].length;
int[][] totalDist = new int[rows][cols]; // 累计所有建筑物到空地的距离和
int mark = 0; // 动态标记值,初始空地值为0
int minDist = Integer.MAX_VALUE;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == 1) {
// 对每个建筑物执行BFS,更新totalDist并动态调整mark
minDist = bfs(grid, totalDist, i, j, mark);
mark--; // 当前建筑物遍历后,mark减1
if (minDist == Integer.MAX_VALUE) return -1; // 提前剪枝
}
}
}
return minDist;
}
private int bfs(int[][] grid, int[][] totalDist, int x, int y, int mark) {
int rows = grid.length;
int cols = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{x, y, 0}); // {x, y, 当前距离}
int minDist = Integer.MAX_VALUE;
while (!queue.isEmpty()) {
int[] curr = queue.poll();
int dist = curr[2]; // 从建筑物到当前位置的距离
for (int[] dir : directions) {
int nx = curr[0] + dir[0];
int ny = curr[1] + dir[1];
// 检查边界和标记:仅处理与当前mark值匹配的空地
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && grid[nx][ny] == mark) {
grid[nx][ny]--; // 更新标记,确保下一建筑只访问此空地
totalDist[nx][ny] += dist + 1; // 累加距离
minDist = Math.min(minDist, totalDist[nx][ny]); // 更新最小距离
queue.offer(new int[]{nx, ny, dist + 1}); // 入队新位置
}
}
}
return minDist;
}
}
代码说明
-
关键变量:
totalDist[][]:记录每个空地到所有建筑物的距离累加和。mark:动态标记值,每遍历一个建筑物后递减,确保后续BFS只处理被之前所有建筑访问过的空地。
-
BFS 过程:
- 从建筑物
(x, y)开始,初始距离为0。 - 遍历四个方向,若相邻位置是当前
mark值的空地(即被之前所有建筑访问过),则更新距离累加和。 - 将新位置入队,并更新其
grid值为mark - 1,使下一轮BFS仅处理更严格条件的空地。
- 从建筑物
-
剪枝逻辑:
- 若某次BFS后
minDist未更新(仍为Integer.MAX_VALUE),说明当前建筑物无法到达之前建筑共同覆盖的空地,直接返回-1。
- 若某次BFS后
亮点:
- 动态标记避免使用额外
visited数组,复用grid减少内存。 - 提前剪枝减少无效计算。
- 队列存储距离代替层序遍历的
size计数,节省计算时间。

8910

被折叠的 条评论
为什么被折叠?



