算法小白学习日记-7:并查集

最近忙于找实习,许久未更新,本次分享有关并查集的几道题目~

并查集的实现

class UnionSet{
public:
    UnionSet(int n):fa(n + 1){
        for(int i = 0; i <= n; i++) fa[i] = i;//初始化父节点序列,每个序列的父节点为它自己
    }    
    int get(int x){
        return fa[x] = (fa[x] == x ? x : gei(fa[x]));//查找某个节点的根节点,在查找的同时剪枝
    }
    void merge(int a, int b){
        fa[get(a)] = get(b);//将a节点的根节点设置为b节点的根节点
    }
    vector<int>fa;
};

由上述代码可以看到,其实并查集的核心代码只有三行(标有注释的三行)。

  1. 第一行是初始化父节点:数组fa代表的是每个几点的根节点,在初始化时让每个节点的父节点都变成其本身;
  2. 第二行是查找根节点:如果某个节点是根节点,则其父节点是其本身。通过这个逻辑查找根节点,在查找的同时实现剪枝,提高后续操作的效率;
  3. 第三行是连接两个节点:直接将a节点的根节点设置为b节点的根节点即可。

题目1:最长连续序列(leetcode - 128)

题目描述

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

样例输入

输入:nums  = [100, 4, 1, 3, 2]

输出:4

解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

题目分析

题目要求时间复杂度为O(n),因此不能用排序算法。可通过并查集,依次扫描集合中的每个元素,查找集合中是否有于其相邻的元素,有则将两个元素连接。

连接完成后,再将最长的子序列找出。

最终代码

class UnionSet {
public :
    UnionSet(int n) : fa(n + 1), size(n + 1) {
        for (int i = 0; i <= n; i++) {
            fa[i] = i;
            size[i] = 1;
        }
    }
    int find(int x) {
        return fa[x] = (fa[x] == x ? x : find(fa[x]));
    }
    int merge(int a, int b) {
        int aa = find(a), bb = find(b);
        if (aa == bb) return 0;
        fa[aa] = bb;
        size[bb] += size[aa];
        return 1;
    }
    vector<int> fa, size;
};

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int n = nums.size(), cnt = 0;
        unordered_map<int, int> h;
        UnionSet u(n);
        for (int i = 0; i < n; i++) {
            int x = nums[i];
            if (h.find(x) != h.end()) continue;
            h[x] = (cnt++);
            if (h.find(x + 1) != h.end()) u.merge(h[x], h[x + 1]);
            if (h.find(x - 1) != h.end()) u.merge(h[x], h[x - 1]);
        }
        int ans = 0;
        for (int i = 0; i < cnt; i++) {
            ans = max(ans, u.size[i]);
        }
        return ans;
    }
};

题目2:被围绕的区域(leetcode - 130)

题目描述

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

样例输入

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

题目分析

可以认为存在一个虚拟外节点,如果某个节点'O'与虚拟外界点连通,则该点不被'X'包围,否则一定会被'X'包围。

可以依次扫描矩阵中的每个元素,如果是'X'则直接跳过,如果是'O',则首先判断是否是边缘的点,如果是则与虚拟外连通。同时,也要扫描该点的右方和下方是否存在'O'节点,存在的话则连通。

最终再依次扫描每个节点,如果是'O'节点, 则判断是否与虚拟外节点连通,连通的话则保留,否则替换为'X'。

最终代码

class UniontSet{//定义并查集
public:
    UniontSet(int n):f(n + 1){
        for(int i = 0; i <= n; i++){
            f[i] = i;
        }
    }
    int find(int n){
        return f[n] = (f[n] == n ? n : find(f[n]));
    }
    void merge(int a, int b){
        if(find(a) == find(b)) return ;
        f[find(a)] = find(b);
        return ;
    }
    vector<int>f;
};

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int n = board.size();
        int m = board[0].size();
        int ind;
        UniontSet u(n * m);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(board[i][j] == 'X') continue;
                ind = i * m + j + 1;//这是一个表示矩阵中每个几点的小技巧
                if(i == 0 || i == n - 1 || j == 0 || j == m - 1) u.merge(ind, 0);
                if(j + 1 < m && board[i][j + 1] == 'O') u.merge(ind, ind + 1);
                if(i + 1 < n && board[i + 1][j] == 'O') u.merge(ind, ind + m);
            }
        }
        
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j ++){
                if(board[i][j] == 'X') continue;
                ind = i * m + j + 1;
                if(u.find(ind) != u.find(0)) board[i][j] = 'X';
            }
        }
        return ;
    }
};

总结

并查集可以方便高效的解决存在连通关系的问题,且并查集的实现也非常简单,三行核心代码一定要理解并记熟。

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值