Java&C++题解与拓展——leetcode934.最短的桥【么的新知识】

91 阅读4分钟
每日一题做题记录,参考官方和三叶的题解

题目要求

image.png

思路:并查集+BFS

  • 核心思想:用并查集构建出来两个岛,然后依次遍历岛上的点向上下左右走,直到走到对方岛屿返回答案。
  • 首先使用并查集构建小岛,也就是将上下左右相连的小岛连通;
    • 此处不需要判断连通的isConnected()
    • find()找爸,union()连通两个点即可。
  • 然后使用队列和哈希表分别存放两个岛屿和不同节点对应步数;
    • 将不同岛屿的节点依次填入相应队列ildild,作为待遍历节点;
    • 同时将节点加入哈希表stp(index,steps)stp(index,steps),并将步数初始化为00
  • 最后开始架桥;
    • 从较小的岛屿开始走【循环次数较少】;
    • 进入update(出发岛, 出发岛节点对应步数, 对方岛)计算步数,针对当前出发岛节点个数进行循环计算:
      • 取队头节点curcur向前(上下左右)走一步到nxtnxt
      • 若越界或还是在自己岛上,则向下一个方向走;
      • 若到达对方岛,则返回所走步数总和,即cur.stp+nxt.stp+1cur.stp+nxt.stp+1
      • 若到河里,则将该节点作为待遍历节点入队到出发岛,并记录对应步数;
        • 延续该步骤结束后,出发岛ildild内为周围最近一圈的河节点。
    • 根据返回值判断:
      • 若到达对方岛,则直接返回结果;
      • 否则重复上述步骤,根据新入队的中间节点【河】进行进一步判断,整个过程可看作两个岛不断向外辐射直至碰到对方领域。

Java

class Solution {
    static int N = 10010;
    static int[] p = new int[N];
    static int[][] dir = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 右左上下
    int n;
    int getIdx(int x, int y) { // 给每个节点赋予一个唯一索引
         return x * n + y;
    }
    int find(int x) {
        if (p[x] != x)
            p[x] = find(p[x]);
        return p[x];
    }
    void union(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    public int shortestBridge(int[][] grid) {
        n = grid.length;
        for (int i = 0; i <= n * n; i++) // 初始化并查集
            p[i] = i;
        for (int i = 0; i < n; i++) { // 构建小岛
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0)
                    continue;
                for (int[] d : dir) {
                    int x = i + d[0], y = j + d[1];
                    if (x < 0 || x >= n || y < 0 || y >= n)
                        continue;
                    if (grid[x][y] == 0)
                        continue;
                    union(getIdx(i, j), getIdx(x, y));
                }
            }
        }
        int a = -1, b = -1;
        Deque<int[]> ild1 = new ArrayDeque<>(), ild2 = new ArrayDeque<>();
        Map<Integer, Integer> stp1 = new HashMap<>(), stp2 = new HashMap<>();
        // 小岛点分别入队,初始化步数
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0)
                    continue;
                int idx = getIdx(i, j), root = find(idx);
                if (a == -1)
                    a = root;
                else if (a != root && b == -1)
                    b = root;
                if (root == a) {
                    ild1.addLast(new int[]{i, j});
                    stp1.put(idx, 0);
                }
                else if (root == b) {
                    ild2.addLast(new int[]{i, j});
                    stp2.put(idx, 0);
                }
            }
        }
        // 架桥
        while (!ild1.isEmpty() && !ild2.isEmpty()) {
            int res = -1;
            if(ild1.size() < ild2.size())
                res = update(ild1, stp1, stp2);
            else
                res = update(ild2, stp2, stp1);
            if (res != -1)
                return res - 1;
        }
        return -1; // 用例保证不出现
    }
    int update(Deque<int[]> ild, Map<Integer, Integer> stp1, Map<Integer, Integer> stp2) {
        int sz = ild.size();
        while (sz-- > 0) {
            int[] cur = ild.pollFirst();
            int x = cur[0], y = cur[1], idx = getIdx(x, y), root = find(idx);
            for (int[] d : dir) {
                int nx = x + d[0], ny = y + d[1], nidx = getIdx(nx, ny);
                if (nx < 0 || nx >= n || ny < 0 || ny >= n)
                    continue;
                if (stp1.containsKey(nidx)) // 还是自己岛
                    continue;
                if (stp2.containsKey(nidx)) // 到达对方岛
                    return stp1.get(idx) + 1 + stp2.get(nidx);
                // 新节点入队
                ild.addLast(new int[]{nx, ny});
                stp1.put(nidx, stp1.get(idx) + 1);
            }
        }
        return -1;
    }
}
  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(n2)O(n^2)

C++

  • update()函数引用忘了&,疯狂超时……
class Solution {
public:
    const static int N = 10010;
    int p[N];
    int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 右左上下
    int n;
    int getIdx(int x, int y) { // 给每个节点赋予一个唯一索引
         return x * n + y;
    }
    int find(int x) {
        if (p[x] != x)
            p[x] = find(p[x]);
        return p[x];
    }
    void unionn(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    int shortestBridge(vector<vector<int>>& grid) {
        n = grid.size();
        for (int i = 0; i <= n * n; i++) // 初始化并查集
            p[i] = i;
        for (int i = 0; i < n; i++) { // 构建小岛
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0)
                    continue;
                for (auto d : dir) {
                    int x = i + d[0], y = j + d[1];
                    if (x < 0 || x >= n || y < 0 || y >= n)
                        continue;
                    if (grid[x][y] == 0)
                        continue;
                    unionn(getIdx(i, j), getIdx(x, y));
                }
            }
        }
        int a = -1, b = -1;
        queue<pair<int, int>> ild1, ild2;
        unordered_map<int, int> stp1, stp2;
        // 小岛点分别入队,初始化步数
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0)
                    continue;
                int idx = getIdx(i, j), root = find(idx);
                if (a == -1)
                    a = root;
                else if (a != root && b == -1)
                    b = root;
                if (root == a) {
                    ild1.emplace(i, j);
                    stp1[idx] = 0;
                }
                else if (root == b) {
                    ild2.emplace(i, j);
                    stp2[idx] = 0;
                }
            }
        }
        // 架桥
        while (!ild1.empty() && !ild2.empty()) {
            int res = -1;
            if(ild1.size() < ild2.size())
                res = update(ild1, stp1, stp2);
            else
                res = update(ild2, stp2, stp1);
            if (res != -1)
                return res - 1;
        }
        return -1; // 用例保证不出现
    }
    int update(queue<pair<int, int>>& ild, unordered_map<int, int>& stp1, unordered_map<int, int>& stp2) {
        int sz = ild.size();
        while (sz-- > 0) {
            auto [x, y] = ild.front();
            ild.pop();
            int idx = getIdx(x, y), root = find(idx);
            for (auto d : dir) {
                int nx = x + d[0], ny = y + d[1], nidx = getIdx(nx, ny);
                if (nx < 0 || nx >= n || ny < 0 || ny >= n)
                    continue;
                if (stp1.count(nidx)) // 还是自己岛
                    continue;
                if (stp2.count(nidx)) // 到达对方岛
                    return stp1[idx] + 1 + stp2[nidx];
                // 新节点入队
                ild.emplace(nx, ny);
                stp1[nidx] = stp1[idx] + 1;
            }
        }
        return -1;
    }
};
  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(n2)O(n^2)

总结

  • 前几天刚学过并查集所以想到了用这个构建小岛,但是架桥还是有点复杂……
  • 其他还有很多方法,比如:
    • DFS+BFS的方法,即用DFS先确定一座岛,然后用BFS向外扩散到另一座岛;
      • 该方法复杂度根据所决定的出发岛存在一定随机性,且需注意标记已遍历位置;
    • 适应性BFS,在插入节点时不是一味向结尾添加,若遇到本岛节点直接向头添加;
  • 一贯的算法题没有心力再搞Rust……

欢迎指正与讨论!