思路
n个节点能不能分为两个集合,每个集合中任意两个节点不存在边相连,二分图问题。
二分图 + 判断奇圈
第一时间想到了图论中判断二分图的方法:图中是否存在奇圈。
以下判断奇圈的方法写的有点粗糙,若有更好的方法希望各位大佬可以分享一下让弟弟学习学习。
第一步:建图:
共n
个结点,边集合为dislikes
数组中的元素,每个元素中两个结点不能分到同一个组中,因此看做一条无向边,采用邻接表双向建图。
第二步:利用DFS判断奇圈。
建完图后,该图中可能存在多个连通块,因此需要遍历所有节点来判断每个连通块内是否存在奇圈,每个连通块有唯一的一个入口节点,即为遍历节点时该连通块内第一个被遍历到的点。
我们以其中的一个连通块为例子:如下图
邻接表双向建图可得:
1: 2,4
2: 1,3
3: 2,4,7
4: 1,3,5,7
5: 4,6
6: 5,7
7: 3,4,6
由于需要额外判断奇圈,所以只利用
visit
数组来标记某个节点是否已经被访问过了是不够的。
本方法中,利用到几个辅助的数组以及变量来判断图中是否有奇圈:
cur
:表示DFS递归过程中地轨道的节点编号;prev
:表示当前节点是遍历到prev
后向下递归的;或者说是在DFS过程中,prev
节点是当前节点的上一层节点(父节点)。该变量防止当前节点重新进入prev
节点;visit[]
:标记数组,用于标记节点是否被访问过,是判断存在圈的关键;distance[]
:距离数组,用于记录当前连通块内从入口节点到某节点间的距离。需要注意的是这里的距离是DFS的距离,相当于DFS深度,而不是计算的最短距离。
综上,DFS的方法签名如下:
bool dfs(vector<vector<int>>& graph, int cur, int prev, int length, bool visit[], int distance[]);
- 邻接表双向建图,得到
graph
; - 初始化
visit
数组,使其元素全部为false
;初始化distance
数组,使其元素全部为0
; - 遍历节点
i
,若visit[i] == false
,则作为它所在连通块的入口节点,进入DFS过程,得到返回值ret
,若ret==false
则直接返回false,否则继续遍历节点。所有节点均遍历完成后均没有返回,则说明不存在奇圈,可返回true
。 - DFS过程:
- 首先是递归退出条件:
visit[cur] == true
,说明递归时遇到了已经访问过的结点,构成了圈,需要判断该圈的长度是否为奇数:return (length - distance[cur]) % 2 == 0
; - 没有进入上述判断,令
visit[cur] = true
,distance[cur] = length
。(这里distance[cur]
就记录了从入口节点i
到当前节点cur
的深度) - 遍历节点
cur
的邻接点,固定节点v
:- 若
v == prev
,则去遍历下一个节点,防止一条边误判成圈情况;否则进入下一步。(这里不能利用visit[v]
来判断是否继续进入DFS过程,否则会导致递归退出条件无法进入,从而无法判圈) - 继续往下层遍历:
dfs(graph, v, cur, length+1, visit, distance)
,若结果为false
,直接返回false
; - 所有邻接节点遍历结束均没有返回的话,说明当前连通块不存在奇圈,返回
true
。
- 若
- 首先是递归退出条件:
以图示为例介绍算法执行过程:
代码如下:
class Solution {
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
//初始化无向图, 两边相连说明不能在同一个组中
vector<vector<int>> graph(n+1, vector<int>());
for (const auto &dislike : dislikes){