Java&C++题解与拓展——leetcode417.太平洋大西洋水流问题【并查集学习与使用】

每日一题做题记录,参考官方和三叶的题解

题目要求

image.png

阅读理解

读了好几遍都没理解是啥意思,然后切到英文看到了both,哦那个加粗的,好的吧……是求既可以流向太平洋又可以流向大西洋的点,一个图遍历题。

image.png

思路一:BFS

  • 逆向思维,从海边推到中心;
  • 分别处理可以流向Pacific和Atlantic的点,将待遍历点压入队列处理:
    • 先找到所有靠海点压入队列;
    • 依次判断该点周围的四个点能否流向自己、最终流向相应海洋。

Java

class Solution {
    int n, m;
    int[][] heights;
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        this.heights = heights;
        m = heights.length;
        n = heights[0].length;
        Deque<int[]> quePac = new ArrayDeque<>(), queAtl = new ArrayDeque<>();
        boolean[][] resP = new boolean[m][n], resA = new boolean[m][n];
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 || j == 0) { // 上边界和左边界,与Pacific连通
                    resP[i][j] = true;
                    quePac.addLast(new int[]{i, j});
                }
                if(i == m -1 || j == n - 1) { // 下边界和右边界,与Atlantic连通
                    resA[i][j] = true;
                    queAtl.addLast(new int[]{i, j});
                }
            }
        }
        BFS(quePac, resP);
        BFS(queAtl, resA);
        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(resP[i][j] && resA[i][j]) { // 都能流到
                    List<Integer> tmp = new ArrayList<>();
                    tmp.add(i);
                    tmp.add(j);
                    res.add(tmp);
                }
            }
        }
        return res;
    }
    
    int[][] dirs = new int[][]{{-1,0}, {0,-1}, {1,0}, {0,1}};
    void BFS(Deque<int[]> que, boolean[][] res) {
        while(!que.isEmpty()) {
            int[] cur = que.pollFirst();
            int x = cur[0], y = cur[1];
            for(int[] d : dirs) { // 东南西北
                int nx = x + d[0], ny = y + d[1];
                if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
                    continue;
                if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                    continue;
                que.addLast(new int[]{nx, ny});
                res[nx][ny] = true;
            }
        }
    }
}
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

C++

class Solution {
    int n, m;
    vector<vector<int>> heights;

    const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        this->heights = heights;
        m = heights.size();
        n = heights[0].size();
        queue<pair<int, int>> quePac, queAtl;
        vector<vector<bool>> resP(m, vector<bool>(n, false));
        vector<vector<bool>> resA(m, vector<bool>(n, false));
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 || j == 0) { // 上边界和左边界,与Pacific连通
                    resP[i][j] = true;
                    quePac.emplace(i, j);
                }
                if(i == m - 1 || j == n - 1) { // 下边界和右边界,与Atlantic连通
                    resA[i][j] = true;
                    queAtl.emplace(i, j);
                }
            }
        }

        BFS(quePac, resP);
        BFS(queAtl, resA);
        vector<vector<int>> res;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(resP[i][j] && resA[i][j]) { // 都能流到
                    vector<int> tmp;
                    tmp.emplace_back(i);
                    tmp.emplace_back(j);
                    res.emplace_back(tmp);
                }
            }
        }
        return res;
    }

    void BFS(queue<pair<int, int>> que, vector<vector<bool>> &res) { // 地址符忘记了,搞死我
        while(!que.empty()) {
            auto [x, y] = que.front();
            que.pop();
            for(auto d : dirs) { // 东南西北
                int nx = x + d[0], ny = y + d[1];
                if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
                    continue;
                if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                    continue;
                que.emplace(nx, ny);
                res[nx][ny] = true;
            }
        }
    }
};
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

思路二:DFS

  • 逆向思维,从海边推到中心;
  • 分别处理可以流向Pacific和Atlantic的点:
    • 判断当前点是否在海边;
    • 判断海边点周围四个点能否流向自己,最终流向海洋;
    • 依次判断相邻、相邻的相邻……

Java

class Solution {
    int n, m;
    int[][] heights;
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        this.heights = heights;
        m = heights.length;
        n = heights[0].length;
        boolean[][] resP = new boolean[m][n], resA = new boolean[m][n];
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 || j == 0)  // 上边界和左边界,与Pacific连通
                    if(!resP[i][j])
                        DFS(i, j, resP);                
                if(i == m -1 || j == n - 1)  // 下边界和右边界,与Atlantic连通
                    if(!resA[i][j])
                        DFS(i, j, resA);                
            }
        }

        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(resP[i][j] && resA[i][j]) { // 都能流到
                    List<Integer> tmp = new ArrayList<>();
                    tmp.add(i);
                    tmp.add(j);
                    res.add(tmp);
                }
            }
        }
        return res;
    }

    int[][] dirs = new int[][]{{-1,0}, {0,-1}, {1,0}, {0,1}};
    void DFS(int x, int y, boolean[][] res) {
        res[x][y] = true;
        for(int[] d : dirs) {
            int nx = x + d[0], ny = y + d[1];
            if(nx < 0 || nx >= m || ny < 0 || ny >= n) //超出边界
                continue;
            if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                continue;
            DFS(nx, ny, res);
        }
    }
}
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

C++

class Solution {
    int n, m;
    vector<vector<int>> heights;

    const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        this->heights = heights;
        m = heights.size();
        n = heights[0].size();
        vector<vector<bool>> resP(m, vector<bool>(n, false));
        vector<vector<bool>> resA(m, vector<bool>(n, false));
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 || j == 0) // 上边界和左边界,与Pacific连通
                    if(!resP[i][j])
                        DFS(i, j, resP);
                if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
                    if(!resA[i][j])
                        DFS(i, j, resA);
            }
        }

        vector<vector<int>> res;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(resP[i][j] && resA[i][j]) { // 都能流到
                    vector<int> tmp;
                    tmp.emplace_back(i);
                    tmp.emplace_back(j);
                    res.emplace_back(tmp);
                }
            }
        }
        return res;
    }

    void DFS(int x, int y, vector<vector<bool>> &res) { // 地址符忘记了,搞死我
        res[x][y] = true;
        for(auto d : dirs) { // 东南西北
            int nx = x + d[0], ny = y + d[1];
            if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
                continue;
            if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                continue;
            DFS(nx, ny, res);
        }
    }
};
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

思路三:并查集+DFS

  • 用并查集维护与海的连通,当然也可以与BFS结合,在此省略。
  • 能流向同一片海的,指向同一个根。

并查集

  • 学习参考链接
  • 处理不相交集合的合并与查询,用数组处理森林,相同集合内的元素为树形,向上指最终到根,根据两个元素指向的根可判断二者是否属于同一集合。
  • 实际可以理解为认爸找爸的问题,合并两个集合,即二者认同一个爸;查询是否属于同一集合,即查询是否是一个爸。

Java

class Solution {
    int N = 200 * 200 + 10;
    int[] pac = new int[N], atl = new int[N];
    int m, n;
    int P, A;
    int[][] heights;

    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        this.heights = heights;
        m = heights.length;
        n = heights[0].length;
        int tot = m * n;
        P = tot + 1; // 流向pacific集合的爸
        A = tot + 2; // 流向atlanti集合的爸
        for(int i = 0; i <= A; i++)
            pac[i] = atl[i] = i;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                int idx = getIdx(i, j);
                if(i == 0 || j == 0)   // 上边界和左边界,与Pacific连通
                    if(!query(pac, P, idx))
                        DFS(pac, P, i, j);
                if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
                    if(!query(atl, A, idx))
                        DFS(atl, A, i, j);
            }
        }

        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                int idx = getIdx(i, j);
                if(query(pac, P, idx) && query(atl, A, idx)) {  // 都能流到
                    List<Integer> tmp = new ArrayList<>();
                    tmp.add(i);
                    tmp.add(j);
                    res.add(tmp);
                }
            }
        }
        return res;
    }

    int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
    void DFS(int[] ocean, int uf, int x, int y) {
        union(ocean, uf, getIdx(x, y));
        for(int[] d : dirs) { // 东南西北
            int nx = x + d[0], ny = y + d[1];
            if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
                continue;
            if(query(ocean, uf, getIdx(nx, ny)) || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                continue;
            DFS(ocean, uf, nx, ny);
        }
    }

    // 并查集
    void union(int[] ocean, int a, int b) { // 并,认爸
        ocean[find(ocean, a)] = ocean[find(ocean, b)];
    }
    int find(int[] ocean, int x) { // 查,找爸
        if(ocean[x] != x)
            ocean[x] = find(ocean, ocean[x]);
        return ocean[x];
    }
    boolean query(int[] ocean, int a, int b) { // 是否有相同爸
        return find(ocean, a) == find(ocean, b);
    }
    int getIdx(int x, int y) { // 唯一编号
        return x * n + y;
    }
}
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

C++

static const int N = 200 * 200 + 10;
class Solution {
    int pac[N], atl[N];
    int m, n;
    int P, A;
    vector<vector<int>> heights;

    const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        this->heights = heights;
        m = heights.size();
        n = heights[0].size();
        int tot = m * n;
        P = tot + 1; // 流向pacific集合的爸
        A = tot + 2; // 流向atlanti集合的爸
        for(int i = 0; i <= A; i++)
            pac[i] = atl[i] = i;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                int idx = getIdx(i, j);
                if(i == 0 || j == 0)   // 上边界和左边界,与Pacific连通
                    if(!query(pac, P, idx))
                        DFS(pac, P, i, j);
                if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
                    if(!query(atl, A, idx))
                        DFS(atl, A, i, j);
            }
        }
        
        vector<vector<int>> res;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                int idx = getIdx(i, j);
                if(query(pac, P, idx) && query(atl, A, idx)) {  // 都能流到
                    vector<int> tmp;
                    tmp.emplace_back(i);
                    tmp.emplace_back(j);
                    res.emplace_back(tmp);
                }
            }
        }
        return res;
    }

    void DFS(int ocean[N], int uf, int x, int y) {
        UNION(ocean, uf, getIdx(x, y));
        for(auto d : dirs) { // 东南西北
            int nx = x + d[0], ny = y + d[1];
            if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
                continue;
            if(query(ocean, uf, getIdx(nx, ny)) || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
                continue;
            DFS(ocean, uf, nx, ny);
        }
    }

    // 并查集
    void UNION(int ocean[N], int a, int b) { // 并,认爸
        ocean[find(ocean, a)] = ocean[find(ocean, b)];
    }
    int find(int ocean[N], int x) { // 查,找爸
        if(ocean[x] != x)
            ocean[x] = find(ocean, ocean[x]);
        return ocean[x];
    }
    bool query(int ocean[N], int a, int b) { // 是否有相同爸
        return find(ocean, a) == find(ocean, b);
    }
    int getIdx(int x, int y) { // 唯一编号
        return x * n + y;
    }
};
  • 时间复杂度: O ( m × n ) O(m\times n) O(m×n)
  • 空间复杂度: O ( m × n ) O(m\times n) O(m×n)

总结

来熟悉一下图的遍历,对遍历搜索代码结构还算熟悉,但套用并不熟练,小细节会丢。

然后学了一下并查集,找爸归祖宗的故事,实现起来简单但应用还是要理解一下的。


欢迎指正与讨论!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值