并查集—岛屿数量 II(leetcode 305)

题目描述

假设你设计一个游戏,用一个 m 行 n 列的 2D 网格来存储你的游戏地图。

起始的时候,每个格子的地形都被默认标记为「水」。我们可以通过使用 addLand 进行操作,将位置 (row, col) 的「水」变成「陆地」。

你将会被给定一个列表,来记录所有需要被操作的位置,然后你需要返回计算出来 每次 addLand 操作后岛屿的数量。

注意:一个岛的定义是被「水」包围的「陆地」,通过水平方向或者垂直方向上相邻的陆地连接而成。你可以假设地图网格的四边均被无边无际的「水」所包围。

请仔细阅读下方示例与解析,更加深入了解岛屿的判定。

示例:

输入: m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]
输出: [1,1,2,3]

解析:

起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)

0 0 0
0 0 0
0 0 0

操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。

1 0 0
0 0 0   Number of islands = 1
0 0 0

操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。

1 1 0
0 0 0   岛屿的数量为 1
0 0 0

操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。

1 1 0
0 0 1   岛屿的数量为 2
0 0 0

操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。

1 1 0
0 0 1   岛屿的数量为 3
0 1 0

拓展:

你是否能在 O(k log mn) 的时间复杂度程度内完成每次的计算?(k 表示 positions 的长度)

算法分析

方法:并查集

把二维的网格图当做一个无向图,横向或者纵向相邻的顶点之间有一条无向边,那么问题就变成了每次 addLand 操作之后在图中寻找连通分量的问题。

算法

对于每个 addLand 操作。需要注意的逻辑是:

    如果 addLand 操作的顶点已经访问过,跳过;
    如果 addLand 操作的顶点没有访问过,此时需要增加连通分量个数,然后再将它与「上」「下」「左」「右」合并。

代码

class Solution {
public:
    vector<int> parents;
    vector<int> ranks;
    vector<bool> status;
    int count;
    void Init(int n) {
        parents.resize(n);
        ranks.resize(n, 1);
        status.resize(n, false);
        count = 0;
        for(int i = 0; i < n; ++i) {
            parents[i] = i;
        }
    }

    int find(int x) {
        if(x == parents[x]) {
            return x;
        }
        parents[x] = find(parents[x]);
        return parents[x];
    }
    void merge(int i, int j) {
        int iroot = find(i);
        int jroot = find(j);
        if(iroot == jroot) {
            return;
        }
        if(ranks[iroot] <= ranks[jroot]) {
            parents[iroot] = jroot;
        } else {
            parents[jroot] = iroot;
        }
        if(ranks[iroot] == ranks[jroot] && iroot != jroot) {
            ranks[jroot]++;
        }
        --count;
    }

    bool isConnected(int i, int j) {
        return find(i) == find(j);
    }

    int getCount() {
        return count;
    }

    bool inArea(int x, int y, int m, int n) {
        if(x >= 0 && x < m && y >= 0 && y < n) {
            return true;
        }
        return false;
    }
    
    vector<int> numIslands2(int m, int n, vector<vector<int>>& positions) {
        int N = m * n;
        Init(N);
        vector<int> res;
        int directions[4][2]  = {{0,  1},
                                {1,  0},
                                {0,  -1},
                                {-1, 0}};
        for(int i = 0; i < positions.size(); ++i) {
            int row = positions[i][0];
            int col = positions[i][1];
            int index = row * n + col;
            if(!status[index]) {
                status[index] = true;
                ++count;
                for(auto &direction: directions) {
                    int newrow = row + direction[0];
                    int newcol = col + direction[1];
                    int newindex = newrow * n + newcol;
                    if(inArea(newrow, newcol, m, n) && status[newindex] && !isConnected(index, newindex)) {
                        merge(index, newindex);
                    }
                }
            }
            res.emplace_back(getCount());
        }
        return res;
    }
};

时间复杂度分析

时间复杂度: O(klog⁡(mn)),其中 k是操作的数目, m是行数, n是列数。
空间复杂度: O(m×n),并查集所需的空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值