零售店取货最短距离

79 阅读6分钟

零售店取货最短距离

问题背景

一家公司的零售店需要从仓库提取货物。我们将零售店和仓库的位置抽象为一个二维矩阵(网格)。我们需要计算所有零售店到其最近仓库的距离总和,以评估整体的物流效率。

地图与移动规则

  1. 地图元素: 矩阵 matrix 中的值有三种含义:

    • 0: 表示一个仓库 (Warehouse)
    • 1: 表示一个零售店 (Retail Store)
    • -1: 表示一个障碍物 (Obstacle)
  2. 移动规则:

    • 只能在相邻的单元格之间上、下、左、右移动。
    • 每次移动的距离计为 1
    • 障碍物所在的单元格无法通过。

任务要求

请计算所有零售店到其最近仓库的最小距离之和。具体规则如下:

  1. 对于地图上的每一个零售店 (1),计算它到所有可达仓库的距离,并找出其中的最小值
  2. 将所有零售店的这个“最小距离”相加,得到最终结果。
  3. 如果某个零售店因为障碍物的阻挡,无法到达任何一个仓库,则该零售店不参与最终的距离和计算。
  4. 如果地图上没有零售店,或者没有任何仓库,则总距离和为 0

输入格式

  • matrix: 一个二维整数数组,代表地图。

    • 1 <= matrix.length, matrix[i].length < 300
    • matrix[i][j] 的值仅为 0, -1, 1

输出格式

  • 一个整数,表示所有零售店到其最近仓库的最小距离之和。

样例说明

样例 1

  • 输入:

    [[1, -1, 0],
     [0, 1, 1],
     [1, -1, 1]]
    
  • 输出: 6

  • 解释:

    该地图上有 5 个零售店和 2 个仓库。我们需要为每个零售店找到其最近的仓库:

    • 零售店 (0, 0): 最近的仓库是 (1, 0),距离为 1
    • 零售店 (1, 1): 最近的仓库是 (1, 0),距离为 1
    • 零售店 (1, 2): 最近的仓库是 (0, 2),距离为 1
    • 零售店 (2, 0): 最近的仓库是 (1, 0),距离为 1
    • 零售店 (2, 2): 到仓库 (0, 2) 的距离是 2,到仓库 (1, 0) 的距离是 3。因此,最近距离为 2

    所有最小距离之和为: 1 + 1 + 1 + 1 + 2 = 6

样例 2

  • 输入:

    [[0, -1, 1],
     [1, -1, 1]]
    
  • 输出: 1

  • 解释:

    • 唯一的仓库位于 (0, 0)
    • 零售店 (1, 0): 可以到达仓库 (0, 0),距离为 1
    • 零售店 (0, 2): 被障碍物 (0, 1)(1, 1) 包围,无法到达任何仓库。不计入总和。
    • 零售店 (1, 2): 同样无法到达任何仓库。不计入总和。

    因此,最终的距离和仅包含可达的零售店 (1, 0) 的距离,结果为 1

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;

/**
 * 解决“最小距离和”问题的方案类。
 */
public class Solution {
    /**
     * 计算所有零售店到最近仓库的最小距离之和。
     *
     * 算法思想:多源广度优先搜索 (Multi-Source BFS)
     * 1.  传统的 BFS 是从一个源点开始,寻找它到其他点的最短路径。
     * 2.  本题要求每个“零售店”到“最近仓库”的距离。如果对每个零售店都跑一次 BFS,在数据量大时会非常耗时。
     * 3.  我们可以逆向思考:将所有“仓库”作为起始源点,同时开始进行 BFS 向外扩散。
     * 4.  BFS 的特性保证了它按层级遍历,所以当从所有仓库同时开始的“波纹”第一次扩散到一个单元格时,
     * 其扩散的步数(距离)就是该单元格到“最近”仓库的距离。
     *
     * 步骤:
     * 1. 初始化:创建一个与地图等大的 `distances` 数组,用于存储每个点到最近仓库的距离,初始值设为一个
     * 特殊值(如 -1)表示未访问。创建一个队列用于 BFS。
     * 2. 找到所有源点:遍历地图,将所有仓库 (值为 0) 的位置加入队列,并将其在 `distances` 数组中
     * 对应位置设为 0。同时,检查地图中是否存在零售店和仓库。
     * 3. 执行 BFS:
     * - 当队列不为空时,出队一个位置 (r, c)。
     * - 遍历其上、下、左、右四个邻居。
     * - 如果邻居位置有效(在边界内、不是障碍、且未被访问过),则更新邻居的距离
     * (distances[neighbor] = distances[current] + 1),并将邻居入队。
 * 4. 统计结果:BFS 结束后,`distances` 数组就存储了所有可达点到最近仓库的距离。 
     * 遍历整个地图,如果某个位置是零售店 (值为 1) 且在 `distances` 中是可达的(值不为 -1),
     * 就将其距离累加到总和中。
     *
     * @param matrix 二维网格,1=零售店, 0=仓库, -1=障碍。
     * @return 所有零售店到最近仓库的最小距离之和。
     */
    public int minDistanceSum(int[][] matrix) {
        // --- 1. 初始化 ---
        // 处理边界情况
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return 0;
        }

        int rows = matrix.length;
        int cols = matrix[0].length;

        // 距离数组,存储每个点到最近仓库的距离。初始化为 -1 表示未访问。
        int[][] distances = new int[rows][cols];
        for (int[] row : distances) {
            Arrays.fill(row, -1);
        }

        // BFS 队列,存储要访问的单元格坐标 [row, col]
        // 使用 ArrayDeque 作为队列,性能通常比 LinkedList 好
        Queue<int[]> queue = new ArrayDeque<>();

        // --- 2. 找到所有源点(仓库),加入队列,并检查是否存在零售店 ---
        boolean hasStores = false;
        boolean hasWarehouses = false;

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (matrix[r][c] == 0) { // 如果是仓库
                    hasWarehouses = true;
                    queue.offer(new int[]{r, c}); // 加入队列作为 BFS 的起始点
                    distances[r][c] = 0; // 仓库到自身的距离为 0
                } else if (matrix[r][c] == 1) {
                    hasStores = true;
                }
            }
        }

        // 根据题目要求,没有零售店或者没有仓库,返回 0
        if (!hasStores || !hasWarehouses) {
            return 0;
        }

        // --- 3. 执行多源 BFS ---
        // 方向数组,方便遍历四个邻居 {上, 下, 左, 右}
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

        while (!queue.isEmpty()) {
            int[] current = queue.poll(); // 取出当前单元格
            int r = current[0];
            int c = current[1];

            // 遍历四个方向
            for (int[] dir : dirs) {
                int nr = r + dir[0]; // 邻居的行
                int nc = c + dir[1]; // 邻居的列

                // 检查邻居是否有效:在边界内、不是障碍、且未被访问过
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols &&
                    matrix[nr][nc] != -1 && distances[nr][nc] == -1) {
                    
                    // 更新邻居的距离 (比当前距离多1)
                    distances[nr][nc] = distances[r][c] + 1;
                    // 将邻居加入队列,待后续处理
                    queue.offer(new int[]{nr, nc});
                }
            }
        }

        // --- 4. 统计结果 ---
        long totalDistanceSum = 0; // 使用 long 防止累加结果溢出 int
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                // 如果当前位置是零售店,并且可以从仓库到达 (距离不是初始值 -1)
                if (matrix[r][c] == 1 && distances[r][c] != -1) {
                    totalDistanceSum += distances[r][c];
                }
            }
        }

        // 题目要求返回 int,对于给定的约束,总和不会溢出 int
        return (int) totalDistanceSum;
    }
}