原文链接: https://leetcode-cn.com/problems/process-restricted-friend-requests
英文原文
You are given an integer n
indicating the number of people in a network. Each person is labeled from 0
to n - 1
.
You are also given a 0-indexed 2D integer array restrictions
, where restrictions[i] = [xi, yi]
means that person xi
and person yi
cannot become friends, either directly or indirectly through other people.
Initially, no one is friends with each other. You are given a list of friend requests as a 0-indexed 2D integer array requests
, where requests[j] = [uj, vj]
is a friend request between person uj
and person vj
.
A friend request is successful if uj
and vj
can be friends. Each friend request is processed in the given order (i.e., requests[j]
occurs before requests[j + 1]
), and upon a successful request, uj
and vj
become direct friends for all future friend requests.
Return a boolean array result
, where each result[j]
is true
if the jth
friend request is successful or false
if it is not.
Note: If uj
and vj
are already direct friends, the request is still successful.
Example 1:
Input: n = 3, restrictions = [[0,1]], requests = [[0,2],[2,1]] Output: [true,false] Explanation: Request 0: Person 0 and person 2 can be friends, so they become direct friends. Request 1: Person 2 and person 1 cannot be friends since person 0 and person 1 would be indirect friends (1--2--0).
Example 2:
Input: n = 3, restrictions = [[0,1]], requests = [[1,2],[0,2]] Output: [true,false] Explanation: Request 0: Person 1 and person 2 can be friends, so they become direct friends. Request 1: Person 0 and person 2 cannot be friends since person 0 and person 1 would be indirect friends (0--2--1).
Example 3:
Input: n = 5, restrictions = [[0,1],[1,2],[2,3]], requests = [[0,4],[1,2],[3,1],[3,4]] Output: [true,false,true,false] Explanation: Request 0: Person 0 and person 4 can be friends, so they become direct friends. Request 1: Person 1 and person 2 cannot be friends since they are directly restricted. Request 2: Person 3 and person 1 can be friends, so they become direct friends. Request 3: Person 3 and person 4 cannot be friends since person 0 and person 1 would be indirect friends (0--4--3--1).
Constraints:
2 <= n <= 1000
0 <= restrictions.length <= 1000
restrictions[i].length == 2
0 <= xi, yi <= n - 1
xi != yi
1 <= requests.length <= 1000
requests[j].length == 2
0 <= uj, vj <= n - 1
uj != vj
中文题目
给你一个整数 n
,表示网络上的用户数目。每个用户按从 0
到 n - 1
进行编号。
给你一个下标从 0 开始的二维整数数组 restrictions
,其中 restrictions[i] = [xi, yi]
意味着用户 xi
和用户 yi
不能 成为 朋友 ,不管是 直接 还是通过其他用户 间接 。
最初,用户里没有人是其他用户的朋友。给你一个下标从 0 开始的二维整数数组 requests
表示好友请求的列表,其中 requests[j] = [uj, vj]
是用户 uj
和用户 vj
之间的一条好友请求。
如果 uj
和 vj
可以成为 朋友 ,那么好友请求将会 成功 。每个好友请求都会按列表中给出的顺序进行处理(即,requests[j]
会在 requests[j + 1]
前)。一旦请求成功,那么对所有未来的好友请求而言, uj
和 vj
将会 成为直接朋友 。
返回一个 布尔数组 result
,其中元素遵循此规则:如果第 j
个好友请求 成功 ,那么 result[j]
就是 true
;否则,为 false
。
注意:如果 uj
和 vj
已经是直接朋友,那么他们之间的请求将仍然 成功 。
示例 1:
输入:n = 3, restrictions = [[0,1]], requests = [[0,2],[2,1]] 输出:[true,false] 解释: 请求 0 :用户 0 和 用户 2 可以成为朋友,所以他们成为直接朋友。 请求 1 :用户 2 和 用户 1 不能成为朋友,因为这会使 用户 0 和 用户 1 成为间接朋友 (1--2--0) 。
示例 2:
输入:n = 3, restrictions = [[0,1]], requests = [[1,2],[0,2]] 输出:[true,false] 解释: 请求 0 :用户 1 和 用户 2 可以成为朋友,所以他们成为直接朋友。 请求 1 :用户 0 和 用户 2 不能成为朋友,因为这会使 用户 0 和 用户 1 成为间接朋友 (0--2--1) 。
示例 3:
输入:n = 5, restrictions = [[0,1],[1,2],[2,3]], requests = [[0,4],[1,2],[3,1],[3,4]] 输出:[true,false,true,false] 解释: 请求 0 :用户 0 和 用户 4 可以成为朋友,所以他们成为直接朋友。 请求 1 :用户 1 和 用户 2 不能成为朋友,因为他们之间存在限制。 请求 2 :用户 3 和 用户 1 可以成为朋友,所以他们成为直接朋友。 请求 3 :用户 3 和 用户 4 不能成为朋友,因为这会使 用户 0 和 用户 1 成为间接朋友 (0--4--3--1) 。
提示:
2 <= n <= 1000
0 <= restrictions.length <= 1000
restrictions[i].length == 2
0 <= xi, yi <= n - 1
xi != yi
1 <= requests.length <= 1000
requests[j].length == 2
0 <= uj, vj <= n - 1
uj != vj
通过代码
高赞题解
拉到最下面还有44ms的超快版本喔!
这道题很明显是并查集,最简单的并查集题目是省份数量,这道题早先的名字叫“朋友圈”。
并查集的核心思想是“同属一个集合的节点拥有相同的根节点”,就像是一个圈子共同拥立一个头头。我们可以利用这个特性,让头头来管理整个圈子的“朋友名单”和“仇人名单”。
那么废话少说,先上代码~!
class Solution {
// 每个人都有一个“大哥”
vector<int> parent;
// 我们可以通过`root()`函数递归找到这个圈子里最大的大哥,也就是圈子的头头。
// 头头没有大哥(parent[x] == -1),或者他的大哥就是他自己(parent[x] == x)
int root(int x) { return (parent[x] == -1 || parent[x] == x) ? x : root(parent[x]); };
public:
vector<bool> friendRequests(int n, vector<vector<int>>& restrictions, vector<vector<int>>& requests) {
// 维护自己朋友圈的朋友名单`friends`,
vector<unordered_set<int>> friends(n);
for (int i = 0; i < n; i++) {
friends[i].insert(i);
}
// 维护自己朋友圈的仇人名单`enemies`,
vector<unordered_set<int>> enemies(n);
for (auto& res : restrictions) {
enemies[res[0]].insert(res[1]);
enemies[res[1]].insert(res[0]);
}
// 开始的时候大家都没有大哥(-1)
parent = vector<int>(n, -1);
auto requests_size = requests.size();
// 我们假定所有交友请求都能成功
vector<bool> result(requests_size, true);
for (int i = 0; i < requests_size; i++) {
// 对于每个交友请求`requests[i]`,先找到两个圈子的头头x和y。
int x = root(requests[i][0]), y = root(requests[i][1]);
// 如果头头是同一个人,说明是同一个圈子。
if (x == y) continue;
// 我判断大哥的方式很粗暴,谁的朋友多谁就是大哥。
// 这是因为后面要遍历friends[y],保证friends[y]的数量比friends[x]小可以提高速度。
if (friends[x].size() < friends[y].size()) swap(x, y);
[&]{
// 头头`x`首先查看`y`提交的朋友名单`friends[y]`
for (auto people : friends[y]) {
// 如果有一个`people`出现在`x`维护的仇人名单`enemies[x]`中
if (enemies[x].count(people) != 0) {
// 交朋友就失败了
result[i] = false;
// 立刻滚蛋(相当于goto匿名函数末尾)
return;
}
}
// `x`把`y`上交的仇人名单`enemies[y]`添加到自己的仇人名单里。
enemies[x].insert(enemies[y].begin(), enemies[y].end());
// `x`把`y`上交的朋友名单`friends[y]`添加到自己的朋友名单里。
friends[x].insert(friends[y].begin(), friends[y].end());
// `y`拜`x`为大哥,这样,`y`的小弟们都会跟着认`x`为头头。
parent[y] = x;
}();
}
return result;
}
};
在并查集中,每个人都有一个“大哥”parent
,
vector<int> parent = vector<int>(n, -1); // 开始时大家都没有大哥,记为-1
朋友圈里最大的大哥就是朋友圈的头头,我们可以通过root()
函数递归找到最大的大哥。
// 头头没有大哥,或者他的大哥就是他自己
int root(int x) { return (parent[x] == -1 || parent[x] == x) ? x : root(parent[x]); };
在一个朋友圈里,所有人的头头都是同一个人。
所以我们可以让头头维护两个列表,一个是这个朋友圈所有人的名单friends
,
vector<unordered_set<int>> friends(n);
for (int i = 0; i < n; i++) {
friends[i].insert(i);
}
一个是这个朋友圈里所有人的仇人enemies
。
vector<unordered_set<int>> enemies(n);
for (auto& res : restrictions) {
enemies[res[0]].insert(res[1]);
enemies[res[1]].insert(res[0]);
}
接下来我们看对交友请求的for
循环。
首先,对于每个交友请求requests[i]
,先把两个圈子的头头x
和y
叫出来。
int x = root(requests[i][0]), y = root(requests[i][1]);
如果头头是同一个人,说明是同一个圈子。那么后面的操作也可以省略了。
if (x == y) continue;
谁的朋友多谁就是大哥,确保friends[x].size() >= friends[y].size()
。
这是因为后面要遍历friends[y]
,保证friends[y]
的数量比friends[x]
小可以提高速度。
if (friends[x].size() < friends[y].size()) swap(x, y);
在C++11中,[&]{ ... }()
代表立即执行的匿名函数。在匿名函数内部return
会直接跳到函数末尾,这样做是为了免去goto
。
头头x
首先检查y
提交的朋友名单friends[y]
,如果有一个people
出现在x
维护的仇人名单enemies[x]
中,交朋友就失败了,立刻滚蛋。
for (auto people : friends[y]) {
if (enemies[x].count(people) != 0) {
result[i] = false;
return;
}
}
交朋友成功之后,头头x
把y
上交的仇人名单enemies[y]
和朋友名单friends[y]
添加到自己的对应名单里。
enemies[x].insert(enemies[y].begin(), enemies[y].end());
friends[x].insert(friends[y].begin(), friends[y].end());
然后y
直接拜x
为大哥,这样,y
的朋友圈的所有人的头头就都变成了x
。
parent[y] = x;
完成!
还可以更快!
由于n不会超过1000个,我们可以用bitset<1000>
代替unordered_set<int>
来减少内存占用,此时大量操作都可以简化为按位操作,速度更快!
class Solution {
vector<int> parent;
int root(int x) { return (parent[x] == -1 || parent[x] == x) ? x : root(parent[x]); };
public:
vector<bool> friendRequests(int n, vector<vector<int>>& restrictions, vector<vector<int>>& requests) {
parent = vector<int>(n, -1);
vector<bitset<1000>> friends(n);
for (int i = 0; i < n; i++) {
friends[i][i] = true;
}
vector<bitset<1000>> enemies(n);
for (auto& res : restrictions) {
enemies[res[0]][res[1]] = true;
enemies[res[1]][res[0]] = true;
}
auto requests_size = requests.size();
vector<bool> result(requests_size, true);
for (int i = 0; i < requests_size; i++) {
int x = root(requests[i][0]), y = root(requests[i][1]);
if (x == y) continue;
// 有没有哪个“y的朋友”同时又是“x的敌人”?
if ((friends[y] & enemies[x]).any()) {
result[i] = false;
continue;
}
// 添加名单只需要按位或就可以了!
enemies[x] |= enemies[y];
friends[x] |= friends[y];
parent[y] = x;
}
return result;
}
};
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
2381 | 4849 | 49.1% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|