零售店取货最短距离
问题背景
一家公司的零售店需要从仓库提取货物。我们将零售店和仓库的位置抽象为一个二维矩阵(网格)。我们需要计算所有零售店到其最近仓库的距离总和,以评估整体的物流效率。
地图与移动规则
-
地图元素: 矩阵
matrix中的值有三种含义:0: 表示一个仓库 (Warehouse) 。1: 表示一个零售店 (Retail Store) 。-1: 表示一个障碍物 (Obstacle) 。
-
移动规则:
- 只能在相邻的单元格之间上、下、左、右移动。
- 每次移动的距离计为
1。 - 障碍物所在的单元格无法通过。
任务要求
请计算所有零售店到其最近仓库的最小距离之和。具体规则如下:
- 对于地图上的每一个零售店 (
1),计算它到所有可达仓库的距离,并找出其中的最小值。 - 将所有零售店的这个“最小距离”相加,得到最终结果。
- 如果某个零售店因为障碍物的阻挡,无法到达任何一个仓库,则该零售店不参与最终的距离和计算。
- 如果地图上没有零售店,或者没有任何仓库,则总距离和为
0。
输入格式
-
matrix: 一个二维整数数组,代表地图。1 <= matrix.length, matrix[i].length < 300matrix[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;
}
}