Given a set of N people (numbered 1, 2, …, N), we would like to split everyone into two groups of any size.
Each person may dislike some other people, and they should not go into the same group.
Formally, if dislikes[i] = [a, b], it means it is not allowed to put the people numbered a and b into the same group.
Return true if and only if it is possible to split everyone into two groups in this way.
Example 1:
Input: N = 4, dislikes = [[1,2],[1,3],[2,4]]
Output: true
Explanation: group1 [1,4], group2 [2,3]
Constraints:
1 <= N <= 2000
0 <= dislikes.length <= 10000
dislikes[i].length == 2
1 <= dislikes[i][j] <= N
dislikes[i][0] < dislikes[i][1]
There does not exist i != j for which dislikes[i] == dislikes[j].
有N个人,可以分成任意数量的两组,但是dislike里面的每一对都不能分到一个组里
思路:
只要保证dislike里面的不在一个组,剩下的人可以随意分组。
所以用dislike里面的边构造无向图,然后给图染两种颜色,dislike里面同一条边的两个端点不能是同一种颜色。
和785题同为给图染色
先用标准BFS,每一层染一种颜色,用color数组记录下每个节点的颜色,当出现颜色冲突时返回false
注意是无向图,两个方向都要加
//28ms
public boolean possibleBipartition(int N, int[][] dislikes) {
if(dislikes == null || dislikes.length == 0) {
return true;
}
HashMap<Integer, List<Integer>> graph = new HashMap<>();
for(int[] dislike : dislikes) {
if(!graph.containsKey(dislike[0])) {
graph.put(dislike[0], new ArrayList<Integer>());
}
graph.get(dislike[0]).add(dislike[1]);
if(!graph.containsKey(dislike[1])) {
graph.put(dislike[1], new ArrayList<Integer>());
}
graph.get(dislike[1]).add(dislike[0]);
}
int[] color = new int[N+1];
for(int i = 1; i <= N; i++) {
if(color[i] == 0 && !bfs(i, graph, color)) {
return false;
}
}
return true;
}
boolean bfs(int start, HashMap<Integer, List<Integer>> graph, int[] color) {
Queue<Integer> queue = new LinkedList<Integer>();
queue.offer(start);
if(color[start] == 0) color[start] = 1;
while(!queue.isEmpty()){
int p1 = queue.poll();
if(!graph.containsKey(p1)) continue;
int nextColor = color[p1] == 1 ? 2 : 1;
for(int p2 : graph.get(p1)) {
if(color[p2] == 0) {
color[p2] = nextColor;
queue.offer(p2);
} else {
if(color[p2] != nextColor) return false;
}
}
}
return true;
}
上面的方法耗时较长,用简易版BFS能缩短3ms
//25ms
public boolean possibleBipartition(int N, int[][] dislikes) {
if(dislikes == null || dislikes.length == 0) {
return true;
}
HashMap<Integer, List<Integer>> graph = new HashMap<>();
for(int[] dislike : dislikes) {
if(!graph.containsKey(dislike[0])) {
graph.put(dislike[0], new ArrayList<Integer>());
}
graph.get(dislike[0]).add(dislike[1]);
if(!graph.containsKey(dislike[1])) {
graph.put(dislike[1], new ArrayList<Integer>());
}
graph.get(dislike[1]).add(dislike[0]);
}
int[] color = new int[N+1];
for(int i = 1; i <= N; i++) {
if(color[i] == 0 && !bfs(i, graph, color, 1)) {
return false;
}
}
return true;
}
boolean bfs(int cur, HashMap<Integer, List<Integer>> graph, int[] color, int curColor) {
if(color[cur] != 0) return color[cur] == curColor;
if(!graph.containsKey(cur)) return true;
color[cur] = curColor;
for(int p2 : graph.get(cur)) {
if(!bfs(p2, graph, color, -curColor)) return false;
}
return true;
}
但是在构建无向图这个过程还是耗时较长,
用Union-Find方法来归类,
首先每个节点的root/parent是它自己,然后遍历dislike的每条边,当一条边右端点的parent是它自己时,更新parent为左端点。把左端点当作右端点的parent。
当右端点的parent不是它自己时,说明它连接的有另一个节点,顺藤摸瓜一直摸到它的root。另外初始颜色设为1,每经过一个parent就换反向的颜色。
这样一个端点找到它的root时,也会得到对应的颜色
当dislike中有一条边,它的左端点和右端点顺藤摸瓜得到的root和颜色都是相同的,说明存在颜色冲突,返回false
//5ms
public boolean possibleBipartition(int N, int[][] dislikes) {
int[] color = new int[N+1];
for(int i = 1; i <= N; i++) color[i] = i;
for(int[] p : dislikes) {
int p1 = p[0];
int p2 = p[1];
if(color[p2] == p2) {
color[p2] = p1;
} else {
int[] rc1 = find(color, p1);
int[] rc2 = find(color, p2);
if(rc1[0] == rc2[0] && rc1[1] == rc2[1]) return false;
}
}
return true;
}
int[] find(int[] colors, int p) {
int color = 1;
while(colors[p] != p) { //not root
p = colors[p];
color = color == 1 ? 2 : 1;
}
return new int[]{p, color};
}