原来这就是并查集

面试问题

我在面试上被问到这么一道题:给你n个数组,一排一个数组,里面放的都是int,比如{1,3,5},里面的数字都是从小到大排序的。如果一个数组里和另一个数组里有相同的数,那么就称他们为同一类。请问,总共有多少不同类的数组?
左思右想想不出来,最开始还以为和从小到大排序有关,以为是关键点,其实没有关。

答案

这是并查集类型的题目。要建一个并查集,通过确定诸多不同根节点来确定数组类型总数。每个数组都是一个节点,小树合并到大树里面。

代码

#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;

class UnionFind {
public:
    UnionFind(int n) : parent(n), rank(n, 0) {
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // Path compression
        }
        return parent[x];
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                ++rank[rootX];
            }
        }
    }

private:
    vector<int> parent;
    vector<int> rank;
};

int countDistinctGroups(vector<vector<int>>& arrays) {
    int n = arrays.size();
    UnionFind uf(n);
    unordered_map<int, int> elementToArray; // Map from element to its array index

    for (int i = 0; i < n; ++i) {
        for (int num : arrays[i]) {
            if (elementToArray.count(num)) {
                uf.unite(i, elementToArray[num]);
            } else {
                elementToArray[num] = i;
            }
        }
    }

    unordered_set<int> uniqueGroups;
    for (int i = 0; i < n; ++i) {
        uniqueGroups.insert(uf.find(i));
    }

    return uniqueGroups.size();
}

int main() {
    vector<vector<int>> arrays = {
        {1, 3, 5},
        {2, 3, 6},
        {7, 8, 9},
        {1, 8}
    };

    cout << "Number of distinct groups: " << countDistinctGroups(arrays) << endl;

    return 0;
}

解释

我的想法:首先,通过map来确定每个元素所在每个原数组,出现相同数字,进到并查集中查看本数组和原初数组的区别,合并等等。进入并查集后,对本数组和原初数组进行对比,确定谁是爸爸,更改parent记录,更改rank层级(这个有什么用?),每次都进去确定爸爸,谁是爸爸,另一个的parent就要进行修改,最后使用set查看parent里面的根节点总数也就是答案。

  1. 并查集类 UnionFind

    • find(int x): 查找元素 x 的根节点,并进行路径压缩。
    • unite(int x, int y): 将包含元素 xy 的集合合并。
  2. 主函数 countDistinctGroups

    • elementToArray: 一个哈希表,映射每个元素到其所在的数组索引。
    • 遍历每个数组中的每个元素,如果该元素之前出现过,则合并当前数组和之前出现该元素的数组。
    • uniqueGroups: 一个哈希集合,用于存储不同集合的根节点。
  3. main函数

    • 提供一个测试用的二维数组,并调用 countDistinctGroups 函数计算不同类的数量。

这个算法的时间复杂度大约为 O(n_m_α(n)),其中 n 是数组数量,m 是每个数组的平均长度,α 是反Ackermann函数,几乎可以看作是常数时间,也就是说复杂度大约为O(n*m)。

并查集

并查集是一种用于处理集合合并和查找问题的数据结构,特别适合解决联通性问题,支持两种主要操作:

Find: 查找某个元素所属的集合(根节点)。
Union: 合并两个元素所在的集合。

这两个操作通常可以在接近常数时间内完成,因为使用了路径压缩(在查找时路径压缩以减少树的深度,find函数)和按秩合并(将较小的树作为子树合并到较大的树上,以保持树的平衡)的优化技术。
c++里没有并查集,boost库里有,boost::disjoint_sets_with_storage。

题目推荐(chat)

以下是一些在LeetCode上类似的算法题目,涉及使用并查集解决集合合并和查询问题的:

Number of Connected Components in an Undirected Graph:
    题目编号:LeetCode 323
    题目描述:给定一个无向图,找出图中的连通分量数量。

Accounts Merge:
    题目编号:LeetCode 721
    题目描述:给定多个账户,每个账户有一个名称和若干个电子邮件。将具有相同电子邮件地址的账户合并。

Redundant Connection:
    题目编号:LeetCode 684
    题目描述:在一个图中,给出了多条边,找出其中一条边,使得删除这条边后,图中不再有环。

Number of Islands:
    题目编号:LeetCode 200
    题目描述:给定一个由 '1'(陆地)和 '0'(水)组成的二维网格地图,计算岛屿的数量。每个岛屿被水包围,并且通过水平或垂直连接相邻的陆地而形成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值