public class 编号2812_找出最安全路径 {
public static void main(String[] args) {
System.out.println(new 编号2812_找出最安全路径().maximumSafenessFactor(Lists.newArrayList(Lists.newArrayList(1, 0, 0), Lists.newArrayList(0, 0, 0), Lists.newArrayList(0, 0, 1))) == 0);
System.out.println(new 编号2812_找出最安全路径().maximumSafenessFactor(Lists.newArrayList(Lists.newArrayList(0, 0, 1), Lists.newArrayList(0, 0, 0), Lists.newArrayList(0, 0, 0))) == 2);
System.out.println(new 编号2812_找出最安全路径().maximumSafenessFactor(Lists.newArrayList(Lists.newArrayList(0, 0, 0, 1), Lists.newArrayList(0, 0, 0, 0), Lists.newArrayList(0, 0, 0, 0), Lists.newArrayList(1, 0, 0, 0))) == 2);
}
/**
* 方向数组,表示上下左右移动
*/
private final static int[][] DIRETIONS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
/**
* 使用多源广度优先搜索(BFS)和并查集两种算法来找出最安全的路径。
*
* @param grid
* @return
*/
public int maximumSafenessFactor(List<List<Integer>> grid) {
// 从输入数据获取网格的大小,用变量n表示,网格是n*n大小
int n = grid.size();
// 定义一个队列(初始化为所有小偷位置),用于多源 BFS 搜索,统计每个单元格到小偷的最小曼哈顿距离
List<int[]> q = new ArrayList<>();
// 每个单元格到小偷的最小曼哈顿距离,初始化为 -1
int[][] dis = new int[n][n];
// 遍历网格
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 如果当前单元格有小偷,则将其位置添加到队列q中
if (grid.get(i).get(j) > 0) {
q.add(new int[]{i, j});
} else {
// 否则,初始化最小曼哈顿距离为-1,表示尚未计算
dis[i][j] = -1;
}
}
}
// 对不同最小曼哈顿距离的位置进行分组(每组的索引就是该组的位置的最小曼哈顿距离)
List<List<int[]>> groups = new ArrayList<>();
// 将所有小偷位置添加到分组列表中(该组的位置的最小曼哈顿距离是0)
groups.add(q);
// 当队列(初始化为所有小偷位置)不为空时,进行多源 BFS 搜索
while (!q.isEmpty()) {
// 临时存储当前队列
var tmp = q;
// 创建新的队列用于下一轮BFS
q = new ArrayList<>();
// 遍历队列中的每个小偷
for (var p : tmp) {
// 遍历当前小偷周围的四个单元格,进行广度优先搜索 (BFS)
for (var d : DIRETIONS) {
// 下一单元格的坐标
int x = p[0] + d[0], y = p[1] + d[1];
// 如果下一单元格在矩阵内,且还未计算最小曼哈顿距离,则加入队列中去,并更新该单元格的最小曼哈顿距离
if (0 <= x && x < n && 0 <= y && y < n && dis[x][y] < 0) {
// 将新位置添加到队列中
q.add(new int[]{x, y});
// 更新该位置的最小曼哈顿距离(就是分组列表的大小,因为每搜索一轮,最小曼哈顿距离+1,这一轮的单元格也全部添加到分组列表中)
dis[x][y] = groups.size();
}
}
}
// 将新的最小曼哈顿距离位置组添加到分组列表中
groups.add(q);
}
// fa:并查集数组,存储每个单元格(对应索引为 i*n+j)的父节点,用于判断起点和终点是否连通
fa = new int[n * n];
// 初始化并查集,每个节点的父节点是它自己
for (int i = 0; i < n * n; i++)
fa[i] = i;
// 从后向前遍历每个最小曼哈顿距离的位置组。即从安全系数最高的位置组开始,逐步降低安全系数,判断起点和终点是否连通
// 注:groups.size()-1 是空数组,没有意义,所以从 groups.size()-2 开始
for (int ans = groups.size() - 2; ans > 0; ans--) {
// 获取当前位置组(当前组的安全系数为ans)
var g = groups.get(ans);
// 遍历当前位置组的每个位置
for (var p : g) {
// 获取当前位置
int i = p[0], j = p[1];
// 遍历每个方向
for (var d : DIRETIONS) {
// 计算下一位置
int ni = i + d[0], nj = j + d[1];
// 如果下一位置在网格内,并且安全系数不低于当前位置,则合并两个位置
if (0 <= ni && ni < n && 0 <= nj && nj < n && dis[ni][nj] >= dis[i][j])
// 合并操作。即将下一位置的根节点的父节点更新为当前位置的根节点
fa[find(ni * n + nj)] = find(i * n + j);
}
}
// 若左上角和右下角单元格是连通的,则返回当前安全系数
if (find(0) == find(n * n - 1))
return ans;
}
// 如果所有安全系数都遍历完,起点和终点仍然不连通,则返回0
return 0;
}
// 并查集数组
private int[] fa;
/**
* 递归查找并查集的祖先(查找单元格x的根节点),压缩路径
*
* @param x
* @return
*/
private int find(int x) {
if (fa[x] != x) {
// 路径压缩,递归找到根节点(将x的父节点直接指向根节点)
fa[x] = find(fa[x]);
}
// 返回x的最终的父节点(直接指向根节点)
return fa[x];
}
}
【数据结构与算法】LeetCode_2812_找出最安全路径
于 2024-05-22 02:05:55 首次发布