面试问题
我在面试上被问到这么一道题:给你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里面的根节点总数也就是答案。
-
并查集类
UnionFind
:find(int x)
: 查找元素x
的根节点,并进行路径压缩。unite(int x, int y)
: 将包含元素x
和y
的集合合并。
-
主函数
countDistinctGroups
:elementToArray
: 一个哈希表,映射每个元素到其所在的数组索引。- 遍历每个数组中的每个元素,如果该元素之前出现过,则合并当前数组和之前出现该元素的数组。
uniqueGroups
: 一个哈希集合,用于存储不同集合的根节点。
-
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'(水)组成的二维网格地图,计算岛屿的数量。每个岛屿被水包围,并且通过水平或垂直连接相邻的陆地而形成。