【困难】力扣算法题解析LeetCode317:离建筑物最近的距离

关注文末推广名片,即可免费获得本题测试源码

题目来源:🔒LeetCode317:离建筑物最近的距离

问题抽象: 给定一个二维网格 grid(包含空地、建筑物和障碍物),要求找到一个 空地块 位置,使其到达所有建筑物的 曼哈顿距离总和最小(若存在解),满足以下核心需求:

  1. 网格元素定义

    • 0 表示空地(可通行);
    • 1 表示建筑物(目标点,不可通行);
    • 2 表示障碍物(不可通行)。
  2. 距离规则

    • 使用 曼哈顿距离(水平+垂直方向步数,忽略对角线);
    • 总距离 = 该空地到所有建筑物的曼哈顿距离之和。
  3. 输出要求

    • 返回最小总距离(整数);
    • 若不存在满足条件的空地(如某建筑物无法被访问),返回 -1
  4. 计算约束

    • 网格尺寸 m×n1 ≤ m, n ≤ 100,建筑物数量 ≥1);
    • 时间复杂度 O(m²n²)(需优化,避免暴力枚举的空地效率);
    • 空间复杂度 O(mn)(存储距离累计值)。
  5. 边界处理

    • 无空地:返回 -1
    • 建筑物不可达:若某建筑物被障碍物隔离(无空地可达),返回 -1
    • 多解情况:仅需最小总距离值(无需输出位置);
    • 单建筑物:总距离即为该空地到唯一建筑的距离。

输入格式:二维整数数组 grid(行优先)
输出:最小总距离(整数)或 -1(无解)。


解题思路

核心:逆向BFS + 动态标记空地

  1. 逆向BFS

    • 从每个建筑物(1)出发进行BFS,计算其到所有空地的距离,并累加到一个总和数组 totalDist 中。
    • 避免从每个空地出发BFS(时间复杂度高),转而从建筑物出发,显著降低计算量。
  2. 动态标记空地

    • 使用 mark 变量标记当前建筑物能到达的空地。初始所有空地值为 0,每遍历完一个建筑物,将其可达的空地值减 1(变为 -1-2等)。
    • 后续BFS仅处理与当前 mark 值匹配的空地,确保只访问被之前所有建筑物访问过的空地。
  3. 剪枝优化

    • 若某次BFS中无法找到与之前建筑物连通的有效空地(res 未更新),直接返回 -1,无需继续计算。

时间复杂度 O ( k ⋅ m ⋅ n ) O(k \cdot m \cdot n) O(kmn),其中 k k k 为建筑物数量, m × n m \times n m×n 为网格大小。 空间复杂度 O ( m ⋅ n ) O(m \cdot n) O(mn),存储 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;
    }
}

代码说明

  1. 关键变量

    • totalDist[][]:记录每个空地到所有建筑物的距离累加和。
    • mark:动态标记值,每遍历一个建筑物后递减,确保后续BFS只处理被之前所有建筑访问过的空地。
  2. BFS 过程

    • 从建筑物 (x, y) 开始,初始距离为 0
    • 遍历四个方向,若相邻位置是当前 mark 值的空地(即被之前所有建筑访问过),则更新距离累加和。
    • 将新位置入队,并更新其 grid 值为 mark - 1,使下一轮BFS仅处理更严格条件的空地。
  3. 剪枝逻辑

    • 若某次BFS后 minDist 未更新(仍为 Integer.MAX_VALUE),说明当前建筑物无法到达之前建筑共同覆盖的空地,直接返回 -1

亮点

  1. 动态标记避免使用额外 visited 数组,复用 grid 减少内存。
  2. 提前剪枝减少无效计算。
  3. 队列存储距离代替层序遍历的 size 计数,节省计算时间。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值