题目描述
给定一组 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;
}
};
运行结果:

博客围绕将人分进两组的问题展开,此为经典二分图题目。解题有两种思路,一是遍历统计连通分量个数,但遍历起点会影响结果;二是先分组再放人,实现相对简单,包括构建邻接链表、染色等步骤,最后给出了代码实现。
581

被折叠的 条评论
为什么被折叠?



