Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:
类型 1:只能由 Alice 遍历。
类型 2:只能由 Bob 遍历。
类型 3:Alice 和 Bob 都可以遍历。
给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
示例 1:
输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,3],[1,2,4],[1,1,2],[2,3,4]]
输出:2
解释:如果删除 [1,1,2] 和 [1,1,3] 这两条边,Alice 和 Bob 仍然可以完全遍历这个图。再删除任何其他的边都无法保证图可以完全遍历。所以可以删除的最大边数是 2 。
示例 2:
输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,4],[2,1,4]]
输出:0
解释:注意,删除任何一条边都会使 Alice 和 Bob 无法完全遍历这个图。
示例 3:
输入:n = 4, edges = [[3,2,3],[1,1,2],[2,3,4]]
输出:-1
解释:在当前图中,Alice 无法从其他节点到达节点 4 。类似地,Bob 也不能达到节点 1 。因此,图无法完全遍历。
提示:
1 <= n <= 10^5
1 <= edges.length <= min(10^5, 3 * n * (n-1) / 2)
edges[i].length == 3
1 <= edges[i][0] <= 3
1 <= edges[i][1] < edges[i][2] <= n
所有元组 (typei, ui, vi) 互不相同
解决方案
并查集方法。首先是 Alice 和 Bob,判断是否能覆盖所有节点,不能则返回-1. 之后,清空数据,只计算3类型的边,记录最多能访问多少个节点count。这些节点组成一个可用公共边互通的子集,子集内遍历访问的边数就是count-1; 再找下一个未访问的节点,并继续形成子集,并把所有的子集边数相加为sumBian = sum(count[i] - 1); 这个sum则是Alice和Bob遍历中,能公用的最大边数。 则Alice和Bob 访问使用的边数量为: common = (n - 1 - sum) + (n - 1 - sum) + sum 最大可删除的边为: edges.length - common = edges.length - (n - 1) * 2 + sum.
class Solution {
int set_num;
int set_num_1, set_num_2, set_num_3 = 0;//集合总数
public int maxNumEdgesToRemove(int n, int[][] edges) {
int[] father = new int[n+1];
ArrayList<Integer> set_label_1 = new ArrayList<>();
ArrayList<Integer> set_label_2 = new ArrayList<>();
ArrayList<Integer> set_label_3 = new ArrayList<>();
// Alice
father = init(n);
for(int i=0; i<edges.length; i++){
if((edges[i][0] == 1) || (edges[i][0] == 3)){
set_num = join(edges[i][1], edges[i][2], set_num, father);
}
}
if(set_num != 1){
System.out.println("Alice");
return -1;
}
// Bob
father = init(n);
for(int i=0; i<edges.length; i++){
if((edges[i][0] == 2) || (edges[i][0] == 3)){
set_num = join(edges[i][1], edges[i][2], set_num, father);
}
}
if(set_num != 1){
System.out.println("Bob");
return -1;
}
// calculate 3rd edges;
father = init(n);
for(int i=0; i<edges.length; i++){
if(edges[i][0] == 3){
set_num = join(edges[i][1], edges[i][2], set_num, father);
set_num_3 = set_num_3 + 1;
// 前两步是判定是否返回-1,在当前已经联通的情况下 先计算3类型的互通的边的数量sum
// 其次alice 和bob连通分别需要的边的数量为n-1,那么最终实际需要的边的数量 (n - 1 -sum) + (n - 1 - sum) + sum (两倍的剪掉在加上)
}
}
int extra_3 = set_num_3 - (n-set_num); //3类型中总计需要n-set_num条3类型的边, 现有set_num_3条边,多余set_num_3 - (n-set_num)条边, 实际需要(n-set_num)
int sum = n-set_num; //实际需要(n-set_num)
int res = edges.length - ((n - 1 - sum)* 2) - sum;
// res = res - extra_3;
return res;
}
public int join(int x,int y, int set_num, int[] father){
int x_root = getRoot(x, father);
int y_root = getRoot(y, father);
if (x_root != y_root){
father[x_root] = y_root;
set_num = set_num - 1;
}
return set_num;
}
public int getRoot(int x, int[] father){
int root = x;
while(father[root] != root){
root = father[root];
}
while(father[x] != root){
int temp = father[x];
father[x] = root;
x = temp;
}
return root;
}
public int[] init(int n){
int[] father = new int[n+1];
set_num = n;
for(int i=1;i<=n;i++){
father[i] = i;
}
return father;
}
}
Tips: 1.前两步是判定是否返回-1,在当前已经联通的情况下 先计算3类型的互通的边的数量sum
2.其次alice 和bob连通分别需要的边的数量为n-1,那么最终实际需要的边的数量 (n - 1 -sum) + (n - 1 - sum) + sum (两倍的剪掉在加上)
计算sum:3类型中总计需要n-set_num条3类型的边, 现有set_num_3条边,多余set_num_3 - (n-set_num)条边, 实际需要(n-set_num)
int sum = n-set_num; //实际需要(n-set_num)
int res = edges.length - ((n - 1 - sum)* 2) - sum;