题目描述
给定一组 n
人(编号为 1, 2, ..., n
), 我们想把每个人分进任意大小的两组。每个人都可能不喜欢其他人,那么他们不应该属于同一组。
给定整数 n
和数组 dislikes
,其中 dislikes[i] = [ai, bi]
,表示不允许将编号为 ai
和 bi
的人归入同一组。当可以用这种方法将所有人分进两组时,返回 true
;否则返回 false
。
示例:
输入:n = 4, dislikes = [[1,2],[1,3],[2,4]]
输出:true
解释:group1 [1,4], group2 [2,3]
解题思路
本题是很经典的二分图题目,如果之前没有接触过二分图,本题还是挺有难度的。
单从图算法题的角度出发,大体上有两种思路:
- 类似于并查集、岛屿问题,遍历所有人后统计剩余的连通分量个数;
- 先分成两组,在考虑放人进组,若分人过程顺利则为真,即二分图。
第一个思路的难点在于:遍历起点不同,最后连通分量数目可能不同。即,可能因为遍历顺序导致原本可以分为两组的情况,被分为更多组!
第二个思路实现起来相对简单:
- 根据
dislikes[]
构建邻接链表graph
; - 分为红蓝两组;
- 初始化
color[]
数组表示各结点尚为染色:下标为结点编号,数组值为颜色0 -- 为染色、1 -- 红色、2 -- 蓝色
; - 随机结点开始染为红色,之后遍历所有未染色的结点,之所以要遍历是因为
graph
可能不是连通图! - 根据邻接链表
graph
对邻居结点染色:对尚未染色的结点染当前结点相反的颜色、对已染色的结点校验本此着色是否于当前颜色冲突; - 存在冲突立即返回
false
; - 完成所有结点染色则返回
true
。
直接上代码,下有更详细的注释。
代码实现
class Solution {
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
//构建邻接链表
vector<vector<int>> graph(n + 1);
for(auto& vec : dislikes){
graph[vec[0]].push_back(vec[1]);
graph[vec[1]].push_back(vec[0]);
}
//初始化color,0表示为染色,1为红色,2为蓝色
vector<int> color(n + 1);
//随机遍历!为方便实现,从1开始顺序遍历
for(int i = 1; i <= n; i++){
//只有未染色结点才有必要初始赋红色,只有dfs()内发生冲突才返回false
if((color[i] == 0) && !dfs(graph, color, i, 1)) return false;
}
//无冲突
return true;
}
bool dfs(vector<vector<int>>& graph, vector<int>& color, int id, int colour){
//染色结点已被染色!校验颜色
if(color[id] != 0) return color[id] == colour;
//结点未染色,为结点染对应的颜色
color[id] = colour;
//邻居节点应染为对立颜色
int neibourColor = colour == 1 ? 2 : 1;
//根据邻接链表为邻居结点染色
for(int& i : graph[id]){
if(!dfs(graph, color, i, neibourColor)) return false;
}
return true;
}
};
运行结果: