并查集+排序,Java双百详细题解
2021年11月28日 14:07
![]()
题意分析
一共有n个专家,最开始只有0号专家知道秘密,在时刻0他会把秘密分享给另一个专家。之后每次开会,知道秘密的专家都会把秘密分享出去。目标是求出最后时刻,一共有多少专家知道秘密。
解题思路
每次分享秘密,可以看作把两个专家合并到一个集合中,因此采用并查集求解。
特别的,本题的目标是求出祖先为0的节点有哪些。
最开始,每个专家的祖先节点记为自己,由于秘密传播是通过会议进行的,时间靠后的会议不可能传播到时间靠前的会议,因此需要先对meetings数组按照会议时间排序。
排序完成后,遍历所有时刻。同一时刻可能存在多场会议,由于秘密共享是瞬时发生的,且同一时刻的会议是乱序的,不存在先后,所以对每一时刻的处理分为两步:
- 第一轮遍历:首先判断两位专家中是否有人知道秘密,若有人知道秘密,则将两位专家的祖先节点都置为0。完成该操作后,无论两位专家是否有人知道秘密,都将两个专家合并,因为同一时刻的其他会议中,可能有其他知道秘密的专家将秘密分享给这两位中的任何一个,若存在此情况,则当前时刻过后,这两位专家也知道了秘密。
- 第二轮遍历:处理两种情况,
- 场景一:第一轮遍历中,先遍历到某场会议,此时两位专家都不知道秘密,但在后面的遍历中,其中一位专家知道了秘密,由于上一步做了合并集合处理,此时将两位专家的祖先节点均置为0即可。
- 场景二:第一轮遍历中,先遍历到某场会议,此时两位专家都不知道秘密,在后面的遍历中,这两位专家均没有被分享秘密,这时需要将两位专家从合并的集合中分离出来,如果不分离出来,在后面某时刻,如果这两位专家其中一个知道了秘密,那么会认为这两位专家都知道了秘密,但事实上,由于该时刻已过去,秘密无法分享给另一位专家。示例2即为此情况。
解题技巧
由于需要按照时间对meetings数组进行升序排序,且之后要按照时间顺序进行遍历。所以考虑使用TreeMap对会议进行存储,key为时刻,value为当前时刻的会议列表。
详细代码
class Solution {
// 并查集数组,记录每个元素的祖先节点
public int[] p;
// 查找每个元素的祖先,(路径压缩,并查集模板)
public int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
public List<Integer> findAllPeople(int n, int[][] meetings, int firstPerson) {
p = new int[n+1];
// 祖先数组初始化,将每个元素的祖先标记为自己
for (int i = 1; i <= n; ++ i) p[i] = i;
// 合并0号专家与firstPerson
p[firstPerson] = 0;
Map<Integer, List<int[]>> map = new TreeMap<>();
// 构造以时刻为key,会议列表为value的Map,TreeMap将自动按照key升序排序
for (int[] m : meetings) {
// m[2]为会议时刻,每个时刻对应多场会议
List<int[]> list = map.getOrDefault(m[2], new ArrayList<>());
list.add(m);
map.put(m[2], list);
}
// 对于每个时刻,遍历两次
for (int x : map.keySet()) {
// 第一轮遍历,合并集合
for (int[] l : map.get(x)) {
int a = l[0], b = l[1];
if (p[find(a)] == 0 || p[find(b)] == 0) { p[find(a)] = 0; p[find(b)] = 0; }
p[find(b)] = p[find(a)];
}
// 第二轮遍历,分场景讨论
for (int[] l : map.get(x)) {
int a = l[0], b = l[1];
// 场景一:两位专家在前面的会议均不知道秘密,后面遍历中其中一位专家知道了秘密,瞬时共享,两人都将知道秘密
if (p[find(a)] == 0 || p[find(b)] == 0) { p[find(a)] = 0; p[find(b)] = 0; }
// 场景二:两位专家在该时刻始终都不知道秘密,将合并的集合分离开,防止后面时刻有一个专家知道秘密,将秘密分享给另一个专家
else { p[a] = a; p[b] = b; }
}
}
List<Integer> ans = new ArrayList<>();
// 祖先为0的元素即为知道秘密的专家
for (int i = 0; i < n; ++ i) {
if (p[find(i)] == 0) ans.add(i);
}
return ans;
}
}