【面试经典 150 | 图】岛屿数量

本文分析了如何利用深度优先搜索(DFS)、广度优先搜索(BFS)和并查集解决经典面试题——计算二维网格中岛屿的数量。作者详细介绍了每种方法的思路、代码实现及时间复杂度,并提供了相应的代码示例。
摘要由CSDN通过智能技术生成

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【图】【深搜】【广搜】【并查集】


题目来源

200. 岛屿数量


解题思路

本题是一道典型的图搜索问题,我们要在图中找到所有的 大 1,所谓的 大 1 就是将图中所有相邻的 1 都标记在一起,比如下图。

我们首先将相邻的 1 合并在一起,然后统计有多少这样的 大 1 存在,即为答案。

以上问题的核心步骤是如何将相邻的 1 和合并在一起,为此有三种方法:

  • 深搜
  • 广搜
  • 并查集

方法一:深搜

思路

numIslands 中我们枚举网格中的每一个位置,如果某位置的字符为 1 并且没有被访问过,则说明此位置是 大 1 的一部分,我们就将统计 大 1 数量(岛屿的数量)的全局变量 ++cnt,接着调用深搜 dfs 函数将与此位置的相连的 1 都连接起来。

最后返回 cnt

代码

class Solution {
public:
    int m, n;
    int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int visited[310][310];

    void dfs(vector<vector<char>>& grid, int x, int y){
        if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0' || visited[x][y]){
            return;
        }

        visited[x][y] = 1;
        for(int i = 0; i < 4; ++i){
            int nx = x + dir[i][0];
            int ny = y + dir[i][1];
            dfs(grid, nx, ny);
        }

    }
    int numIslands(vector<vector<char>>& grid) {
        m = grid.size();
        n = grid[0].size();
        int cnt = 0;
        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                if(grid[i][j] == '1' && !visited[i][j]){
                    cnt++;
                    dfs(grid, i, j);
                }
            }
        }
        return cnt;
    }
};

复杂度分析

时间复杂度: O ( m n ) O(mn) O(mn) m m m n n n 分别为网格的行数和列数。

空间复杂度: O ( m n ) O(mn) O(mn)

方法二:广搜

利用广搜也可以将相连的 1 连接起来形成 大 1。以下代码是利用广搜将遍历到的 大 1 的第一个 1 不动,与之相连的 1 置为字符 0,从而统计岛屿的数量。

代码

class Solution {
private:
    const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int cnt = 0;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == '1') {
                    ++cnt;
                    grid[i][j] = '0';
                    queue<pair<int, int>> nb;
                    nb.push({i, j});
                    while (!nb.empty()) {
                        auto ij = nb.front(); nb.pop();
                        for (int k = 0; k < 4; ++k) {
                            int nx = ij.first + dirs[k][0];
                            int ny = ij.second + dirs[k][1];
                            if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == '1') {
                                grid[nx][ny] = '0';
                                nb.push({nx, ny});
                            }
                        }
                    }
                }
            }
        }
        return cnt;
    }
};

复杂度分析

时间复杂度: O ( m n ) O(mn) O(mn) m m m n n n 分别为网格的行数和列数。

空间复杂度: O ( m i n ( m , n ) ) O(min(m, n)) O(min(m,n)),在最坏的情况下,整个网络均为陆地,队列的大小可以达到 m i n ( m , n ) min(m, n) min(m,n)

方法三:并查集

牵涉到 合并 的问题,一定不能忘记并查集。关于并查集方法与使用可以参考 【并查集(上)基础篇】【并查集(下)应用篇】 这两篇文章。

落实到本题的实现中,在并查集模板中增加一个 res 变量,用来统计矩阵中 ‘1’ 的数量,在原数组中,枚举到 ‘1’ 的存在就 + + r e s ++res ++res。在合并 ‘1’ 的时候就 − − r e s --res res,最后返回 r e s res res。具体可以参考我的并查集方法代码。

代码

class Solution {
public:
    const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int res = 0;
        vector<int> pa(m*n);
        vector<int> rank(m*n, 0);
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == '1') {
                    ++res;
                }
                pa[i * n + j] = i * n + j;
            }
        }
        

        function<int(int)> find = [&](int x) {
            return pa[x] == x ? x : pa[x] = find(pa[x]);
        };

        function<void(int, int)> unite = [&](int x, int y) {
            x = find(x);
            y = find(y);
            if (x == y) return;
            if (rank[x] < rank[y]) {
                swap(x, y);
                }
                pa[y] = x;
                if (rank[x] == rank[y]) rank[x] += 1;
                --res;
        };

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == '1') {
                    grid[i][j] == '0';
                    for (int k = 0; k < 4; ++k) {
                        int x = i + dirs[k][0];
                        int y = j + dirs[k][1];
                        if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') {
                            unite(i * n + j, x * n + y);
                        }
                    }
                }
            }
        }
        return res;
    }
};

并查集的时间复杂度这里不做研究,感兴趣的可以参考官方题解。


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值