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]

Example 2:

Input: N = 3, dislikes = [[1,2],[1,3],[2,3]]
Output: false

Example 3:

Input: N = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]
Output: false

Note:

  1. 1 <= N <= 2000
  2. 0 <= dislikes.length <= 10000
  3. 1 <= dislikes[i][j] <= N
  4. dislikes[i][0] < dislikes[i][1]
  5. There does not exist i != j for which dislikes[i] == dislikes[j].
这道题又是关于二分图的题,第一次接触的时候是 Is Graph Bipartite?,那道题给的是建好的邻接链表(虽然是用数组实现的),但是本质上和这道题是一样的,同一条边上的两点是不能在同一个集合中的,那么这就相当于本题中的 dislike 的关系,也可以把每个 dislike 看作是一条边,那么两端的两个人不能在同一个集合中。看透了题目的本质后,就不难做了,跟之前的题相比,这里唯一不同的就是邻接链表没有给我们建好,需要自己去建。不管是建邻接链表,还是邻接矩阵都行,反正是要先把图建起来才能遍历。那么这里我们先建立一个邻接矩阵好了,建一个大小为 (N+1) x (N+1) 的二维数组g,其中若 g[i][j] 为1,说明i和j互相不鸟。那么先根据 dislikes 的情况,把二维数组先赋上值,注意这里 g[i][j] 和 g[j][i] 都要更新,因为是互相不鸟,而并不是某一方热脸贴冷屁股。下面就要开始遍历了,还是使用染色法,使用一个一维的 colors 数组,大小为 N+1,初始化是0,由于只有两组,可以用1和 -1 来区分。那么开始遍历图中的结点,对于每个遍历到的结点,如果其还未被染色,还是一张白纸的时候,调用递归函数对其用颜色1进行尝试染色。在递归函数中,现将该结点染色,然后就要遍历所有跟其合不来的人,这里就发现邻接矩阵的好处了吧,不然每次还得遍历 dislikes 数组。由于这里是邻接矩阵,所以只有在其值为1的时候才处理,当找到一个跟其合不来的人,首先检测其染色情况,如果此时两个人颜色相同了,说明已经在一个组里了,这就矛盾了,直接返回 false。如果那个人还是白纸一张,我们尝试用相反的颜色去染他,如果无法成功染色,则返回 false。循环顺序退出后,返回 true,参见代码如下:

解法一:

class Solution {
public:
bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
vector<vector<int>> g(N + , vector<int>(N + ));
for (auto dislike : dislikes) {
g[dislike[]][dislike[]] = ;
g[dislike[]][dislike[]] = ;
}
vector<int> colors(N + );
for (int i = ; i <= N; ++i) {
if (colors[i] == && !helper(g, i, , colors)) return false;
}
return true;
}
bool helper(vector<vector<int>>& g, int cur, int color, vector<int>& colors) {
colors[cur] = color;
for (int i = ; i < g.size(); ++i) {
if (g[cur][i] == ) {
if (colors[i] == color) return false;
if (colors[i] == && !helper(g, i, -color, colors)) return false;
}
}
return true;
}
};
我们还可以用迭代的写法,不实用递归函数,但是整个思路还是完全一样的。这里建立邻接链表,比邻接矩阵能省一些空间,只把跟其相邻的结点存入对应的数组内。还是要建立一个一维 colors 数组,并开始遍历结点,若某个结点已经染过色了,跳过,否则就先给其染为1。然后借助 queue 来进行 BFS 遍历,现将当前结点排入队列,然后开始循环队列,取出队首结点,然后遍历其所有相邻结点,如果两个颜色相同,直接返回 false,否则若其为白纸,则赋相反颜色,并且排入队列。最终若顺序完成遍历,返回true,参见代码如下:
解法二:
class Solution {
public:
bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
vector<vector<int>> g(N + );
for (auto dislike : dislikes) {
g[dislike[]].push_back(dislike[]);
g[dislike[]].push_back(dislike[]);
}
vector<int> colors(N + );
for (int i = ; i <= N; ++i) {
if (colors[i] != ) continue;
colors[i] = ;
queue<int> q{{i}};
while (!q.empty()) {
int t = q.front(); q.pop();
for (int cur : g[t]) {
if (colors[cur] == colors[t]) return false;
if (colors[cur] == ) {
colors[cur] = -colors[t];
q.push(cur);
}
}
}
}
return true;
}
};
其实这道题还可以使用并查集 Union Find 来做,所谓的并查集,简单来说,就是归类,将同一集合的元素放在一起。那么如何在能验证两个元素是否属于同一个集合呢,这里就要使用一个 root 数组(有时候是使用 HashMap),如果两个元素是同一个组的话,那么最终调用find函数返回的值应该是相同的,可以理解为老祖宗相同就是同一个组,两个点的 root 值不同,也可能是同一个组,因为 find 函数的运作机制是一直追根溯源到最原始的值。可以看到,这里博主的 find() 函数写的是递归形式,一行搞定碉堡了,当然也有 while 循环式的迭代写法。好,回过头来继续说这道题,这里还是首先建图,这里建立邻接链表,跟上面的使用二维数组的方法不同,这里使用来 HashMap,更加的节省空间。现在不需要用 colors 数组了,而是要使用并查集需要的 root 数组,给每个点都初始化为不同的值,因为在初始时将每个点都看作一个不同的组。然后开始遍历所有结点,若当前结点没有邻接结点,直接跳过。否则就要开始进行处理了,并查集方法的核心就两步,合并跟查询。我们首先进行查询操作,对当前结点和其第一个邻接结点分别调用find函数,如果其返回值相同,则意味着其属于同一个集合了,这是不合题意的,直接返回 false。否则继续遍历其他的邻接结点,对于每一个新的邻接结点,都调用 find() 函数,还是判断若返回值跟原结点的相同,return false。否则就要进行合并操作了,根据敌人的敌人就是朋友的原则,所有的邻接结点之间应该属于同一个组,因为就两个组,我所有不爽的人都不能跟我在一个组,那么他们所有人只能都在另一个组,所以需要将他们都合并起来,合并的时候不管是用 root[parent] = y 还是 root[g[i][j]] = y 都是可以,因为不管直接跟某个结点合并,或者跟其祖宗合并,最终经过 find() 函数追踪溯源都会返回相同的值,参见代码如下:
解法三:
class Solution {
public:
bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
unordered_map<int, vector<int>> g;
for (auto dislike : dislikes) {
g[dislike[]].push_back(dislike[]);
g[dislike[]].push_back(dislike[]);
}
vector<int> root(N + );
for (int i = ; i <= N; ++i) root[i] = i;
for (int i = ; i <= N; ++i) {
if (!g.count(i)) continue;
int x = find(root, i), y = find(root, g[i][]);
if (x == y) return false;
for (int j = ; j < g[i].size(); ++j) {
int parent = find(root, g[i][j]);
if (x == parent) return false;
root[parent] = y;
}
}
return true;
}
int find(vector<int>& root, int i) {
return root[i] == i ? i : find(root, root[i]);
}
};
讨论:可以看到本文中的三种解法在建立图的时候,使用的数据结构都不同,解法一使用二维数组建立了邻接矩阵,解法二使用二维数组建立了邻接链表,解法三使用了 HashMap 建立了邻接链表。刻意使用不同的方法就是为了大家可以对比区别一下,这三种方法都比较常用,在不同的题目中选择最适合的方法即可。

Github 同步地址:

类似题目:

参考资料:

https://leetcode.com/problems/possible-bipartition/

https://leetcode.com/problems/possible-bipartition/discuss/159085/java-graph

https://leetcode.com/problems/possible-bipartition/discuss/195303/Java-Union-Find

https://leetcode.com/problems/possible-bipartition/discuss/158957/Java-DFS-solution

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Possible Bipartition 可能的二分图的更多相关文章

  1. [LeetCode] Is Graph Bipartite? 是二分图么?

    Given an undirected graph, return true if and only if it is bipartite. Recall that a graph is bipart ...

  2. [LeetCode] 785. Is Graph Bipartite? 是二分图么?

    Given an undirected graph, return true if and only if it is bipartite. Recall that a graph is bipart ...

  3. 【LeetCode】886. Possible Bipartition 解题报告(Python)

    [LeetCode]886. Possible Bipartition 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu ...

  4. LeetCode 886. Possible Bipartition

    原题链接在这里:https://leetcode.com/problems/possible-bipartition/ 题目: Given a set of N people (numbered 1, ...

  5. leetcode 890. Possible Bipartition

    Given a set of N people (numbered 1, 2, ..., N), we would like to split everyone into two groups of ...

  6. leetcode.图.785判断二分图-Java

    1. 具体题目 给定一个无向图graph,当这个图为二分图时返回true.如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这 ...

  7. Java实现 LeetCode 785 判断二分图(分析题)

    785. 判断二分图 给定一个无向图graph,当这个图为二分图时返回true. 如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我 ...

  8. [leetcode]785. Is Graph Bipartite? [bai'pɑrtait] 判断二分图

    Given an undirected graph, return true if and only if it is bipartite. Example 1: Input: [[1,3], [0, ...

  9. Swift LeetCode 目录 | Catalog

    请点击页面左上角 -> Fork me on Github 或直接访问本项目Github地址:LeetCode Solution by Swift    说明:题目中含有$符号则为付费题目. 如 ...

随机推荐

  1. ABP代码生成器与升级到VS2017VSIX

    首先,我不是要分享一个代码生成器,而是怎么升级到VS2017,简单介绍下 如何将2015的VSIX项目升级到2017 阳光铭睿 写了一篇<分享一个与ABP配套使用的代码生成器源码>,并在群 ...

  2. 【JS】VUE学习

    VUE的全家桶:vue-cli,vue-router,vue-resource,vuex 环境搭建:https://www.jianshu.com/p/32beaca25c0d 先码在这儿吧. htt ...

  3. The Quad - Directory Explorer(一款四窗口的文件资源管理器)

    官网:http://www.q-dir.com/ 参考这位兄弟的介绍:https://www.cnblogs.com/clso/p/4694486.html 一款四窗口的文件资源管理器.

  4. composer操作简单解析

    1. composer配置中国镜像 #使用命令: composer config -e#修改composer.json 添加如下代码 { "repositories": [ { & ...

  5. TFS2015创建项目

    1,在TFS服务器上的团队项目集合中创建集合   2,创建集合完毕后,在VS2017中选择管理连接,创建对应的管理连接.     3,团队资源管理器中新建团队项目.后续就是下一步,下一步完成.帐号权限 ...

  6. 高性能HTTP加速器Varnish-3.0.3搭建、配置及优化步骤

    经过一天的努力,终于将Varnish缓存服务器部署到线上服务器了.趁着热乎劲儿,赶紧给大家分享一下.Varnish是一个轻量级的Cache和反向代理软件.先进的设计理念和成熟的设计框架是Varnish ...

  7. 结对开发项目--石家庄地铁web版

    一.功能要求 1.数据库设计:将石家庄地铁线路图的各个线路,各个站点,换乘信息等用数据库的形式保存起来,应该保存的信息有 {线路号,线路的各个站名,车站的换乘信息}. 2.站点查询:用户可以输入任一一 ...

  8. spring-boot-starter-actouator2.1.4与c3p0版本0.9.1.2冲突

    报错前的pom文件: <?xml version="1.0" encoding="UTF-8"?><project xmlns="h ...

  9. linux中sogou输入法崩溃重启

    经常在linux中搜狗输入法用着用着就崩溃了,无法输入中文,又不想重启电脑,照着下面在终端输入命令可以重启输入法: 1.先关闭fcitx(小企鹅输入法,提供了良好的中文输入法环境) # killall ...

  10. python之lambda函数

    今天复习python,看见一个关于lambda函数的例子,在python中使用lambda在某些时候十分方便,因为不必为了实现某些简单功能而新建一个函数.但是有这么一个lambda实例令我有些疑惑,现 ...