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 代表「陆地」)
- 操作 #1addLand(0, 0) 将 grid[0][0] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #2addLand(0, 1) 将 grid[0][1] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #3addLand(1, 2) 将 grid[1][2] 的水变为陆地。此时存在 2 个岛屿。
- 操作 #4addLand(2, 1) 将 grid[2][1] 的水变为陆地。此时存在 3 个岛屿。

在这里插入图片描述

题目解析

并查集

和其他的不同是,这里的并查集是动态生成的:填海造陆

实现一


class Solution {

    class UnionFind{
    private:
        std::vector<int> parent;
        std::vector<int> size;
        std::vector<int> help;
        const int row, col;
        int cnt;
    public:

        UnionFind(int m, int n) : row(m), col(n), cnt(0){
            parent.resize(row * col);
            size.resize(row * col);
            help.resize(row * col);
        }


        int connect(int r, int c){
        	// 计算出(r, c)对应的坐标
            int i = index(r, c);
            if(size[i] == 0){   // 如果这不是海洋,那么可以填
                // 填海造陆
                parent[i] = i;  // 填海
                size[i] = 1;    // 填海
                ++cnt;		    // 填海
                // 将新造的陆地 与四周的陆地打通(如果能够打通的就打通)
                merge(r - 1, c, r, c);
                merge(r + 1, c, r, c);
                merge(r, c - 1, r, c);
                merge(r, c + 1, r, c);
            }
            return cnt;
        }
     

    private:
        void merge(int r1, int c1, int r2, int c2){
           // 超出边界了,什么也不干
            if(r1 < 0 || r1 >= row || r2 < 0 || r2 >= row
               || c1 < 0 || c1 >= col || c2 < 0 || c2 >= col){
                return ;
            }


            // 计算对应的索引,如果发现某一个索引位置是海洋,那么就不需要联通
            int i1 = index(r1, c1), i2 = index(r2, c2);
            if(size[i1] == 0 || size[i2] == 0){
                return;
            }

            // 找代表节点
            int f1 = findRoot(i1), f2 = findRoot(i2);
            // 如果它们属于同一个领主,那么直接返回
            if(f1 == f2){
                return;
            }

           //大的领主打败小的领主
            if (size[f1] >= size[f2]) {
                size[f1] += size[f2];
                parent[f2] = f1;
            } else {
                size[f2] += size[f1];
                parent[f1] = f2;
            }
            cnt--;
        }
        int index(int i, int j) const{
            return i * col + j;
        }

        int findRoot(int i){
            int hi = 0;
            while (i != parent[i]){
                help[hi++] = i;
                i = parent[i];
            }
            for ( hi--; hi >= 0; --hi) {
                parent[help[hi]] = i;
            }
            return i;
        }


    };

public:
    std::vector<int> numIslands(int m, int n, std::vector<std::vector<int>> positions) {
        UnionFind unionFind(m, n);
        std::vector<int> ans;
        for(auto position : positions){
            int cnt = unionFind.connect(position[0], position[1]);
            ans.emplace_back(cnt);
        }
        return ans;
    }
};

实现二

如果m*n比较大,会经历很重的初始化,怎么优化呢?

注意,下面的可能会超时

class Solution {

    class UnionFind{
    private:
        std::unordered_map<std::string, std::string> parent;
        std::unordered_map<std::string, int> size;
        std::vector<std::string> help;
        int cnt;
    public:
        explicit UnionFind() : cnt(0){
        }
        
        bool connect(int r, int c){
            std::string key = std::to_string(r) + "_" + std::to_string(c);
            if(parent.count(key) == 0){
                parent[key] = key;
                size[key] = 1;
                ++cnt;
                std::string up = std::to_string(r - 1) + "_" + std::to_string(c);
                std::string down = std::to_string(r + 1) + "_" + std::to_string(c);
                std::string left = std::to_string(r) + "_" + std::to_string(c - 1);
                std::string right = std::to_string(r) + "_" + std::to_string(c + 1);
                merge(key, up);
                merge(key, down);
                merge(key, left);
                merge(key, right);
            }
            return cnt;
        }

    private:
        std::string findRoot(std::string cur){
            while (cur != parent[cur]){
                help.emplace_back(cur);
                cur = parent[cur];
            }
            for(auto &str : help){
                parent[str] = cur;
            }
            return cur;
        }

        void merge(const std::string& s1, const std::string& s2){
            // 如果发现某一个位置是海洋,那么就不需要联通
            if(parent.count(s1) || parent.count(s2)){
                return;
            }

            std::string f1 = findRoot(s1), f2 = findRoot(s2);
            if(f1 == f2){
                return;
            }
            
            int size1 = size[f1], size2 = size[f2];
            std::string big = size1 >= size2 ? f1 : f2;
            std::string small = big == f1 ? f2 : f1;
            parent[small] = big;
            size[big] += size[small];
            --cnt;
        }
        
    };

public:
    std::vector<int> numIslands(int m, int n, std::vector<std::vector<int>> positions) {
        UnionFind unionFind;
        std::vector<int> ans;
        for(auto position : positions){
            int cnt = unionFind.connect(position[0], position[1]);
            ans.emplace_back(cnt);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值