代码随想录第52天(补充)|103. 水流问题、102. 沉没孤岛

参考资料:代码随想录

102. 沉没孤岛

给定一个由0和1组成的矩阵,在垂直和水平方向垂直方向相邻的陆地单元格组成的区域,岛屿指的是完全被水域包围的地方

现在你需要知道孤岛是不靠近岛边且被水域包围的岛屿,现在你需要沉没所有孤岛,就是将所有陆地单元格变成水域单元格。孤岛是位于矩阵内部。所有单元格都不接触边缘的岛屿。

现在有N和M,N表示行数M表示列数,之后N行表示M个数字,数字为0或者1,表示岛屿的单元格。

思路分析

思路依然是从地图周边出发,然后将周边空格相邻的陆地都做上标记,然后遍历一遍地图,把陆地改成水域就可以。

终止条件是再遍历一次地图,遇到陆地且没有做过标记的,全部改成水域就可以。

有的人可能回想定义一个visited[][],单调标记周边的陆地然后遍历 visited[][]和陆地, 然后遍历的时候进行判断,决定陆地是否变成水域。

这样做其实有点麻烦了,不用额为的空间,直接改陆地为其他特殊值作为标记就可以。

步骤一 深度搜索或者广度搜索把边上的陆地改为2,中间的陆地改为1,其他水域改为0就可以了。

步骤二 将水域中间1(陆地) 改为水域0,

步骤三 将之前标记 的2改为1陆地。

#include <iostream>
#include <vector>
using namespace std;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
void dfs(vector<vector<int>>& grid, int x, int y) {
    grid[x][y] = 2;
    for (int i = 0; i < 4; i++) { // 向四个方向遍历
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        // 超过边界
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;
        // 不符合条件,不继续遍历
        if (grid[nextx][nexty] == 0 || grid[nextx][nexty] == 2) continue;
        dfs (grid, nextx, nexty);
    }
    return;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    // 步骤一:
    // 从左侧边,和右侧边 向中间遍历
    for (int i = 0; i < n; i++) {
        if (grid[i][0] == 1) dfs(grid, i, 0);
        if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);
    }

    // 从上边和下边 向中间遍历
    for (int j = 0; j < m; j++) {
        if (grid[0][j] == 1) dfs(grid, 0, j);
        if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);
    }
    // 步骤二、步骤三
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) grid[i][j] = 0;
            if (grid[i][j] == 2) grid[i][j] = 1;
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }
}

遇到的问题

  1. int nextx = x + dir[i][0]; // i不是0 1 2 3 吗?和四个方向有什么关系?
  • i是循环变量,表示当前的方向索引。
  • 根据 i 的值,从 dir 数组中取出对应的位移量 {x_offset, y_offset},计算下一个位置 (nextx, nexty)

具体对应关系如下:

  • i = 0 时,dir[0]{ -1, 0 },表示向上移动。
  • i = 1 时,dir[1]{ 0, -1 },表示向左移动。
  • i = 2 时,dir[2]{ 1, 0 },表示向下移动。
  • i = 3 时,dir[3]{ 0, 1 },表示向右移动。

假设当前坐标是 (x, y) = (2, 3),我们通过 dir 数组计算四个方向的下一个位置:

idir[i]计算公式下一个位置 (nextx, nexty)
0{ -1, 0 }(2 + (-1), 3 + 0)(1, 3)
1{ 0, -1 }(2 + 0, 3 + (-1))(2, 2)
2{ 1, 0 }(2 + 1, 3 + 0)(3, 3)
3{ 0, 1 }(2 + 0, 3 + 1)(2, 4)

通过这种方式,我们可以遍历当前位置的四个相邻方向。

103. 水流问题

现在有一个N*M的矩阵,里面有很多单元格,每个单元格里面包含一个数值。单元格里的数值代表它的相对高度,矩阵的左边界和上边界为它的第一组边界,矩阵的右下为第二边界。矩阵会向下倾斜,但是只能从较高的点向较低的点流动。从单元格出发的水可以达到左上或者右下,这样的。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。

img

比如蓝色方块的水既能流向第一组边界,又能流向第二组边界,所有最终的答案为蓝色方块的所有坐标。

思路分析

怎么去思考这个问题,就是遍历每一个点,可以考虑用BFS或者DFS,只能能到达到第一梯队,或者第二梯队都可以。

这样做很显然会超时,时间复杂度为n * m ,因为每次搜索每个节点都要遍历一遍,都要深搜,

可以反向思考一下,从边界出发。从第一个边界出发逆流而上,把节点都遍历上。从第二个节点出发逆流而上,把节点都遍历上。

两方都遍历过的节点就是我们要求的部分,这部分既可以流向第一组边界,又可以流向第二组边界。

img

这样就可以求出交界节点,这样就能写出详细代码和注释。

#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y]) return;

    visited[x][y] = true;

    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
        if (grid[x][y] > grid[nextx][nexty]) continue; // 注意:这里是从低向高遍历

        dfs (grid, visited, nextx, nexty);
    }
    return;
}



int main() {

    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> firstBorder(n, vector<bool>(m, false));

    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> secondBorder(n, vector<bool>(m, false));

    // 从最上和最下行的节点出发,向高处遍历
    for (int i = 0; i < n; i++) {
        dfs (grid, firstBorder, i, 0); // 遍历最左列,接触第一组边界
        dfs (grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
    }

    // 从最左和最右列的节点出发,向高处遍历
    for (int j = 0; j < m; j++) {
        dfs (grid, firstBorder, 0, j); // 遍历最上行,接触第一组边界
        dfs (grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 如果这个节点,从第一组边界和第二组边界出发都遍历过,就是结果
            if (firstBorder[i][j] && secondBorder[i][j]) cout << i << " " << j << endl;;
        }
    }


}

新手误区

时间复杂度为O(n * m) ,然后就dfs的事件复杂度,主函数中dfs还有for循环。

这里其实有个误区因为visited[][]函数会记录已经走过的地方,走过的节点是不会再走的。

需要看传入的参数是什么,如果是传入数组 firstBorder,地图中的每一个节点需要遍历一次,无论你调用几次。同理参数传入的是 数组 secondBorder,地图中每个节点也只会遍历一次。

所以时间复杂度为O(n * m * 2) ,参数传入 firstBorder 和 secondBorder 各遍历一次。地图用每个节点就遍历了两次

// 从最上和最下行的节点出发,向高处遍历
for (int i = 0; i < n; i++) {
    dfs (grid, firstBorder, i, 0); // 遍历最左列,接触第一组边界
    dfs (grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
}

// 从最左和最右列的节点出发,向高处遍历
for (int j = 0; j < m; j++) {
    dfs (grid, firstBorder, 0, j); // 遍历最上行,接触第一组边界
    dfs (grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
}

这里是每天回答一个问题
如果觉得对你有帮助的话
可以点一个免费的赞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值