Article / 文章
LeetCode 第317题:离建筑物最近的距离
你是个房地产开发商,想要选择一片空地 建一栋大楼。你想把这栋大楼够造在一个距离周边设施都比较方便的地方,通过调研,你希望从它出发能到达所有的建筑物,并且该大楼到每个建筑物的距离之和最小。 给你一个由 0、1 和 2 组成的二维网格,其中: - 0 代表你可以自由通过和选择建造的空地 - 1 代表你无法通过的建筑物 - 2 代表你无法通过的障碍物
📖 文章摘要
本文详细解析LeetCode第317题“离建筑物最近的距离”,这是一道图论和广度优先搜索的问题。文章提供了基于多源BFS的解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合想要提升图论算法能力的程序员。
核心知识点: 广度优先搜索、图论、矩阵遍历
难度等级: 困难
推荐人群: 具有图论基础,想要提升算法能力的程序员
题目描述
你是个房地产开发商,想要选择一片空地 建一栋大楼。你想把这栋大楼够造在一个距离周边设施都比较方便的地方,通过调研,你希望从它出发能到达所有的建筑物,并且该大楼到每个建筑物的距离之和最小。
给你一个由 0、1 和 2 组成的二维网格,其中:
- 0 代表你可以自由通过和选择建造的空地
- 1 代表你无法通过的建筑物
- 2 代表你无法通过的障碍物
示例
示例 1:
输入:grid = [[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]]
输出:7
解释:
所有建筑物的位置为 (0,0), (0,4), (2,2)
最优位置为 (1,2) ,距离之和为 7
示例 2:
输入:grid = [[1,0]]
输出:1
示例 3:
输入:grid = [[1]]
输出:-1
提示
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 50
- grid[i][j] 是 0, 1 或 2
- grid 中至少有一栋建筑物
解题思路
方法:多源BFS
这道题可以使用多源BFS来解决。我们需要从每个建筑物出发,计算到每个空地的距离。
关键点:
- 从每个建筑物开始进行BFS
- 记录每个空地到所有建筑物的距离之和
- 记录每个空地能够到达的建筑物数量
- 最终结果必须能到达所有建筑物
具体步骤:
- 统计建筑物总数
- 对每个建筑物进行BFS:
- 计算到每个空地的距离
- 更新每个空地的总距离
- 更新每个空地能到达的建筑物数量
- 找到能到达所有建筑物且总距离最小的空地
时间复杂度:O(m * n * k),其中k是建筑物数量 空间复杂度:O(m * n)
图解思路
BFS过程分析表
| 步骤 | 当前建筑物 | 当前层级 | 访问的空地 | 更新的距离 |
|---|---|---|---|---|
| 初始状态 | (0,0) | 0 | [] | [] |
| 第一步 | (0,0) | 1 | [(0,1), (1,0)] | [1,1] |
| 第二步 | (0,0) | 2 | [(1,1)] | [2] |
| 第三步 | (0,4) | 1 | [(0,3), (1,4)] | [1,1] |
| 第四步 | (0,4) | 2 | [(1,3)] | [2] |
距离统计表
| 位置 | 到建筑物1距离 | 到建筑物2距离 | 到建筑物3距离 | 总距离 |
|---|---|---|---|---|
| (1,2) | 2 | 2 | 3 | 7 |
| (1,1) | 2 | 3 | 4 | 9 |
| (1,3) | 3 | 2 | 2 | 7 |
代码实现
C# 实现
public class Solution {
private int m, n;
private int[][] directions = new int[][] {
new int[] {-1, 0},
new int[] {1, 0},
new int[] {0, -1},
new int[] {0, 1}
};
public int ShortestDistance(int[][] grid) {
m = grid.Length;
n = grid[0].Length;
int buildingCount = 0;
// 记录到每个建筑物的距离之和
int[,] totalDist = new int[m,n];
// 记录能到达的建筑物数量
int[,] reachCount = new int[m,n];
// 统计建筑物数量
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
buildingCount++;
}
}
}
// 从每个建筑物进行BFS
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
BFS(grid, i, j, totalDist, reachCount);
}
}
}
// 找到最小距离
int minDist = int.MaxValue;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0 && reachCount[i,j] == buildingCount) {
minDist = Math.Min(minDist, totalDist[i,j]);
}
}
}
return minDist == int.MaxValue ? -1 : minDist;
}
private void BFS(int[][] grid, int row, int col, int[,] totalDist, int[,] reachCount) {
Queue<(int, int)> queue = new Queue<(int, int)>();
bool[,] visited = new bool[m,n];
int dist = 0;
queue.Enqueue((row, col));
visited[row,col] = true;
while (queue.Count > 0) {
int size = queue.Count;
dist++;
for (int i = 0; i < size; i++) {
var (curRow, curCol) = queue.Dequeue();
foreach (var dir in directions) {
int newRow = curRow + dir[0];
int newCol = curCol + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n
&& !visited[newRow,newCol] && grid[newRow][newCol] == 0) {
totalDist[newRow,newCol] += dist;
reachCount[newRow,newCol]++;
visited[newRow,newCol] = true;
queue.Enqueue((newRow, newCol));
}
}
}
}
}
}
Python 实现
class Solution:
def shortestDistance(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
directions = [(-1,0), (1,0), (0,-1), (0,1)]
# 统计建筑物数量
building_count = sum(1 for i in range(m) for j in range(n) if grid[i][j] == 1)
# 记录到每个建筑物的距离之和和能到达的建筑物数量
total_dist = [[0] * n for _ in range(m)]
reach_count = [[0] * n for _ in range(m)]
def bfs(row: int, col: int) -> None:
queue = [(row, col)]
visited = [[False] * n for _ in range(m)]
visited[row][col] = True
dist = 0
while queue:
dist += 1
next_queue = []
for cur_row, cur_col in queue:
for dx, dy in directions:
new_row, new_col = cur_row + dx, cur_col + dy
if (0 <= new_row < m and 0 <= new_col < n and
not visited[new_row][new_col] and grid[new_row][new_col] == 0):
total_dist[new_row][new_col] += dist
reach_count[new_row][new_col] += 1
visited[new_row][new_col] = True
next_queue.append((new_row, new_col))
queue = next_queue
# 从每个建筑物进行BFS
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
bfs(i, j)
# 找到最小距离
min_dist = float('inf')
for i in range(m):
for j in range(n):
if grid[i][j] == 0 and reach_count[i][j] == building_count:
min_dist = min(min_dist, total_dist[i][j])
return min_dist if min_dist != float('inf') else -1
C++ 实现
class Solution {
private:
vector<vector<int>> directions = {{-1,0}, {1,0}, {0,-1}, {0,1}};
int m, n;
void bfs(vector<vector<int>>& grid, int row, int col,
vector<vector<int>>& totalDist, vector<vector<int>>& reachCount) {
queue<pair<int,int>> q;
vector<vector<bool>> visited(m, vector<bool>(n, false));
int dist = 0;
q.push({row, col});
visited[row][col] = true;
while (!q.empty()) {
int size = q.size();
dist++;
for (int i = 0; i < size; i++) {
auto [curRow, curCol] = q.front();
q.pop();
for (const auto& dir : directions) {
int newRow = curRow + dir[0];
int newCol = curCol + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n
&& !visited[newRow][newCol] && grid[newRow][newCol] == 0) {
totalDist[newRow][newCol] += dist;
reachCount[newRow][newCol]++;
visited[newRow][newCol] = true;
q.push({newRow, newCol});
}
}
}
}
}
public:
int shortestDistance(vector<vector<int>>& grid) {
m = grid.size();
n = grid[0].size();
int buildingCount = 0;
vector<vector<int>> totalDist(m, vector<int>(n, 0));
vector<vector<int>> reachCount(m, vector<int>(n, 0));
// 统计建筑物数量
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
buildingCount++;
}
}
}
// 从每个建筑物进行BFS
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
bfs(grid, i, j, totalDist, reachCount);
}
}
}
// 找到最小距离
int minDist = INT_MAX;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0 && reachCount[i][j] == buildingCount) {
minDist = min(minDist, totalDist[i][j]);
}
}
}
return minDist == INT_MAX ? -1 : minDist;
}
};
执行结果
C# 实现
- 执行用时:156 ms
- 内存消耗:42.8 MB
Python 实现
- 执行用时:892 ms
- 内存消耗:17.2 MB
C++ 实现
- 执行用时:68 ms
- 内存消耗:16.4 MB
性能对比
| 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| C# | 156 ms | 42.8 MB | 实现简洁,性能适中 |
| Python | 892 ms | 17.2 MB | 代码最简洁,但性能较差 |
| C++ | 68 ms | 16.4 MB | 性能最优,内存占用最小 |
代码亮点
- 🎯 使用多源BFS高效解决问题
- 💡 通过两个矩阵分别记录距离和可达性
- 🔍 优化剪枝:只统计能到达所有建筑物的位置
- 🎨 代码结构清晰,变量命名规范
常见错误分析
- 🚫 忽略了某些空地可能无法到达所有建筑物
- 🚫 BFS过程中的访问标记处理不当
- 🚫 距离计算错误或重复计算
- 🚫 没有正确处理边界情况
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 多源BFS | O(mnk) | O(m*n) | 直观易懂,实现简单 | 当建筑物较多时效率较低 |
| 动态规划 | O(mnk) | O(m*n) | 可以复用中间结果 | 实现复杂,不易理解 |
相关题目
- LeetCode 286. 墙与门 - 中等
- LeetCode 542. 01 矩阵 - 中等
- LeetCode 994. 腐烂的橘子 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新至第317题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!