【C/C++】417. 太平洋大西洋水流问题

272 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情


题目链接:417. 太平洋大西洋水流问题

题目描述

有一个 m × n 的矩形岛屿,与 太平洋大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。

这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度

岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。

返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋

提示:

  • m == heights.length
  • n == heights[r].length
  • 1m,n2001 \leqslant m, n \leqslant 200
  • 0heights[r][c]1050 \leqslant heights[r][c] \leqslant 10^5

示例 1:

waterflow-grid.jpg

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

示例 2:

输入: heights = [[2,1],[1,2]]
输出: [[0,0],[0,1],[1,0],[1,1]]

整理题意

题目给了一个 m × n 的矩形岛屿,“太平洋” 处于矩形岛屿的左边界和上边界,而 “大西洋” 处于矩形岛屿的右边界和下边界。矩形岛屿中的每个单元格有一个整数,表示矩形岛屿该单元格位置 高于海平面的高度 。因为水往低处流的缘故,题目问哪些单元格中的水 既可流向太平洋也可流向大西洋

解题思路分析

习惯性动作,首先观察题目数据:

  • 矩形岛屿最大为 200 * 200。(不算很大)
  • 矩形岛屿中每个单元格最大值为 10510^5。( INT 数据范围内) 对于有图和树的题,一般情况都是需要遍历每个节点的,所以通常会使用到搜索算法 BFSDFS

正向思维: 遍历每一个点,模拟该点的流动,水往低处流,向低于当前单元格四周单元格流动,最后记录既可流向太平洋也可流向大西洋的点。显然这样做会导致重复遍历很多点,导致时间复杂度很高。正繁则反 的思想,我们考虑逆向思维。

逆向思维: 正向思维是水往低处流,那么逆向思维自然是水往高处流,反向搜索 。我们从岛屿的边界逆向流动,向四周高于当前单元格的单元格流动,我们利用 示例 1 的图,通过逆向思维,每个单元格向四周较高的单元格流动,我们大致可以得到这样一幅流向图:

微信截图_20220427180410.png

由图可以看出如果该单元格既可以从太平洋反向到达也可以从大西洋反向到达,那么必定也能从该单元格正向流动到太平洋和大西洋。

具体实现

该题我们既可以使用 DFS,也可以使用 BFS 进行搜索,核心思想在于搜索的方向:反向搜索

  • 首先从矩形岛屿的上边界和左边界开始反向搜索,记录能够从太平洋反向搜索到的单元格。
  • 然后从矩形岛屿的下边界和右边界开始反向搜索,记录能够从大西洋反向搜索到的单元格。
  • 遍历所有单元格,将既可以从太平洋反向到达也可以从大西洋反向到达的单元格加入答案数组。

复杂度分析

  • 时间复杂度:O(mn)O(mn),其中 mn 分别是矩阵 heights 的行数和列数。广度和深度优先搜索最多遍历每个单元格两次,寻找太平洋和大西洋都可以到达的单元格需要遍历整个矩阵,因此时间复杂度是 O(mn)O(mn)
  • 空间复杂度:O(mn)O(mn),其中 mn 分别是矩阵 heights 的行数和列数。广度优先搜索的队列空间是 O(mn)O(mn),深度优先搜索的递归调用层数是 O(mn)O(mn),记录每个单元格是否可以到达太平洋和大西洋需要 O(mn)O(mn) 的空间,因此空间复杂度是 O(mn)O(mn)

代码实现

class Solution {
private:
    int dx[4] = {0, 1, 0, -1};
    int dy[4] = {1, 0, -1, 0};
    int n, m;
    bool check(int x, int y){
        if(x < 0 || x >= n || y < 0 || y >= m) return false;
        return true;
    }
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        n = heights.size();
        m = heights[0].size();
        //mp1记录能流入太平洋的点,mp2记录能流入大西洋的点
        vector<vector<bool>> mp1(n, vector<bool>(m, false));
        vector<vector<bool>> mp2(n, vector<bool>(m, false));
        //bfs反向逆流到山顶
        queue<pair<int, int>> que;
        while(que.size()) que.pop();
        //将靠近太平洋的点放入队列作为起点
        for(int i = 0; i < n; i++){
            if(!mp1[i][0]){
                mp1[i][0] = true;
                que.push(make_pair(i, 0));
            }
        }
        for(int i = 0; i < m; i++){
            if(!mp1[0][i]){
                mp1[0][i] = true;
                que.push(make_pair(0, i));
            }
        }
        //bfs能够流入太平洋的点
        while(que.size()){
            pair<int, int> now = que.front();
            que.pop();
            for(int i = 0; i < 4; i++){
                int nx = dx[i] + now.first;
                int ny = dy[i] + now.second;
                if(check(nx, ny) && !mp1[nx][ny]
                && heights[nx][ny] >= heights[now.first][now.second]){
                    mp1[nx][ny] = true;
                    que.push(make_pair(nx, ny));
                }
            }
        }
        //再将靠近大西洋的点放入队列作为起点
        for(int i = 0; i < n; i++){
            if(!mp2[i][m - 1]){
                mp2[i][m - 1] = true;
                que.push(make_pair(i, m - 1));
            }
        }
        for(int i = 0; i < m; i++){
            if(!mp2[n - 1][i]){
                mp2[n - 1][i] = true;
                que.push(make_pair(n - 1, i));
            }
        }
        //bfs能够流入大西洋的点
        while(que.size()){
            pair<int, int> now = que.front();
            que.pop();
            for(int i = 0; i < 4; i++){
                int nx = dx[i] + now.first;
                int ny = dy[i] + now.second;
                if(check(nx, ny) && !mp2[nx][ny]
                && heights[nx][ny] >= heights[now.first][now.second]){
                    mp2[nx][ny] = 1;
                    que.push(make_pair(nx, ny));
                }
            }
        }
        //遍历所有点,将能够流入太平洋和大西洋的点放入答案数组
        vector<vector<int>> ans;
        ans.clear();
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(mp1[i][j] && mp2[i][j]){
                    ans.push_back({i, j});
                }
            }
        }
        return ans;
    }
};

总结

该题使用到了 正繁则反 的逆向思维,考虑问题时可以从多角度出发。在本题中我们的认知是水往低处流,这是题目的外表,只是将水的模型加在了题目上,题目还可以将模型换成其他东西,核心在于只能从高到低。我们不能被题目的描述所限制,尝试不同的角度去考虑,在代码中水往高处流也不是不可。

在这样的分析之后,该题就是简单的图搜索和遍历,在搜索过程中注意判断方向和边界问题即可。


结束语

同一个问题,有的人可以很快找到答案,有的人花了几天也没有头绪。有时候,成功迟迟不来,不是你不够努力,而是思维方式要更新。别在日复一日的生活中形成无意识的惯性,换种思维,天地可能更开阔。