【数据结构与算法】LeetCode_2812_找出最安全路径

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];
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值