[LeetCode] Possible Bipartition 可能的二分图

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 + 1, vector<int>(N + 1));
        for (auto dislike : dislikes) {
            g[dislike[0]][dislike[1]] = 1;
            g[dislike[1]][dislike[0]] = 1;
        }
        vector<int> colors(N + 1);
        for (int i = 1; i <= N; ++i) {
            if (colors[i] == 0 && !helper(g, i, 1, colors)) return false;
        }
        return true;
    }
    bool helper(vector<vector<int>>& g, int cur, int color, vector<int>& colors) {
        colors[cur] = color;
        for (int i = 0; i < g.size(); ++i) {
            if (g[cur][i] == 1) {
                if (colors[i] == color) return false;
                if (colors[i] == 0 && !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 + 1);
        for (auto dislike : dislikes) {
            g[dislike[0]].push_back(dislike[1]);
            g[dislike[1]].push_back(dislike[0]);
        }
        vector<int> colors(N + 1);
        for (int i = 1; i <= N; ++i) {
            if (colors[i] != 0) continue;
            colors[i] = 1;
            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] == 0) {
                        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[0]].push_back(dislike[1]);
            g[dislike[1]].push_back(dislike[0]);
        }
        vector<int> root(N + 1);
        for (int i = 1; i <= N; ++i) root[i] = i;
        for (int i = 1; i <= N; ++i) {
            if (!g.count(i)) continue;
            int x = find(root, i), y = find(root, g[i][0]);
            if (x == y) return false;
            for (int j = 1; 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建立了邻接链表。刻意使用不同的方法就是为了大家可以对比区别一下,这三种方法都比较常用,在不同的题目中选择最适合的方法即可。

类似题目:

Is Graph Bipartite?

参考资料:

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 题目讲解汇总(持续更新中...)

原文地址:https://www.cnblogs.com/grandyang/p/10317141.html

时间: 2024-10-29 03:58:48

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

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

Given an undirected graph, return true if and only if it is bipartite. Recall that a graph is bipartite if we can split it's set of nodes into two independent subsets A and B such that every edge in the graph has one node in A and another node in B.

[leetcode]785. Is Graph Bipartite? [bai&#39;pɑrtait] 判断二分图

Given an undirected graph, return true if and only if it is bipartite. Example 1: Input: [[1,3], [0,2], [1,3], [0,2]] Output: true Explanation: The graph looks like this: 0----1 | | | | 3----2 We can divide the vertices into two groups: {0, 2} and {1

leetcode 890. Possible Bipartition

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

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

1. 具体题目 给定一个无向图graph,当这个图为二分图时返回true.如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图.graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点.每个节点都是一个在0到graph.length-1之间的整数.这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值. 示例 1: 输入: [[1,3], [0,2],

785. 判断二分图——本质上就是图的遍历 dfs或者bfs

785. 判断二分图 给定一个无向图graph,当这个图为二分图时返回true. 如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图. graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点.每个节点都是一个在0到graph.length-1之间的整数.这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值. 示例 1: 输入: [[1,3], [

[LeetCode] 349 Intersection of Two Arrays &amp; 350 Intersection of Two Arrays II

这两道题都是求两个数组之间的重复元素,因此把它们放在一起. 原题地址: 349 Intersection of Two Arrays :https://leetcode.com/problems/intersection-of-two-arrays/description/ 350 Intersection of Two Arrays II:https://leetcode.com/problems/intersection-of-two-arrays-ii/description/ 题目&解法

LeetCode 442. Find All Duplicates in an Array (在数组中找到所有的重复项)

Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once. Find all the elements that appear twice in this array. Could you do it without extra space and in O(n) runtime? Example: Input: [4,3,2,7,

LeetCode OJ - Sum Root to Leaf Numbers

这道题也很简单,只要把二叉树按照宽度优先的策略遍历一遍,就可以解决问题,采用递归方法越是简单. 下面是AC代码: 1 /** 2 * Sum Root to Leaf Numbers 3 * 采用递归的方法,宽度遍历 4 */ 5 int result=0; 6 public int sumNumbers(TreeNode root){ 7 8 bFSearch(root,0); 9 return result; 10 } 11 private void bFSearch(TreeNode ro

LeetCode OJ - Longest Consecutive Sequence

这道题中要求时间复杂度为O(n),首先我们可以知道的是,如果先对数组排序再计算其最长连续序列的时间复杂度是O(nlogn),所以不能用排序的方法.我一开始想是不是应该用动态规划来解,发现其并不符合动态规划的特征.最后采用类似于LRU_Cache中出现的数据结构(集快速查询和顺序遍历两大优点于一身)来解决问题.具体来说其数据结构是HashMap<Integer,LNode>,key是数组中的元素,所有连续的元素可以通过LNode的next指针相连起来. 总体思路是,顺序遍历输入的数组元素,对每个