题目形容

这是 LeetCode 上的 886. 可能的二分法 ,难度为 中等

Tag : 「二分图」、「染色法」、「并查集」、「DFS」

给定一组 n 人(编号为 1, 2, ..., n), 咱们想把每个人分进任意大小的两组。每个人都可能不喜爱其他人,那么他们不应该属于同一组。

给定整数 n 和数组 dislikes ,其中 $dislikes[i] = [a_i, b_i]$ ,示意不容许将编号为 $a_i$ 和  $b_i$ 的人纳入同一组。当能够用这种办法将所有人分进两组时,返回 true;否则返回 false

示例 1:

输出:n = 4, dislikes = [[1,2],[1,3],[2,4]]输入:true解释:group1 [1,4], group2 [2,3]

示例 2:

输出:n = 3, dislikes = [[1,2],[1,3],[2,3]]输入:false

示例 3:

输出:n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]输入:false

提醒:

  • $1 <= n <= 2000$
  • $0 <= dislikes.length <= 10^4$
  • $dislikes[i].length = 2$
  • $1 <= dislikes[i][j] <= n$
  • $a_i < b_i$
  • dislikes 中每一组都 不同

染色法

无论是从题目形容和对点边的形容,这都是一道「染色法断定二分图」的模板题。

为了不便,咱们令 dislikesds,将其长度记为 $m$。

题目要求咱们将 $n$ 个点划分到两个汇合中,同时咱们将每个 $ds[i]$ 看做无向边的话,可知汇合外部无际,即所有的边必然横跨两个汇合之间。

应用 $ds[i]$ 进行建图,并将两个将要划分出的两个汇合别离记为 AB,咱们能够采纳「染色」的形式,尝试将所有点进行划分。

构建一个与点数相等的数组 color,咱们人为规定划分到汇合 A 的点满足 $color[i] = 1$,划分到汇合 B 的点满足 $color[i] = 2$,起始有 $color[i] = 0$,代表该点尚未被划分。

随后咱们能够实现 DFS 函数为 boolean dfs(int u, int cur) 含意为尝试将点 ucur 色。依据定义可知,咱们除了须要 color[u] = cur 以外,还须要遍历点 u 的所有出边(解决其邻点,将其划分到另一汇合上),若在处理过程中发生冲突,则返回 false,若能顺利染色则返回 true

因为咱们固定了色彩编号为 12,因而 cur 的对抗色可对立为 3 - cur

最终,咱们依据是否给所有点染色胜利来决定答案。

Java 代码:

class Solution {    int N = 2010, M = 2 * 10010;    int[] he = new int[N], e = new int[M], ne = new int[M], color = new int[N];    int idx;    void add(int a, int b) {        e[idx] = b;        ne[idx] = he[a];        he[a] = idx++;    }    boolean dfs(int u, int cur) {        color[u] = cur;        for (int i = he[u]; i != -1; i = ne[i]) {            int j = e[i];            if (color[j] == cur) return false;            if (color[j] == 0 && !dfs(j, 3 - cur)) return false;        }        return true;    }    public boolean possibleBipartition(int n, int[][] ds) {        Arrays.fill(he, -1);        for (int[] info : ds) {            int a = info[0], b = info[1];            add(a, b); add(b, a);        }        for (int i = 1; i <= n; i++) {            if (color[i] != 0) continue;            if (!dfs(i, 1)) return false;        }        return true;    }}

TypeScript 代码:

const N = 2010, M = 2 * 10010const he = new Array<number>(N), e = new Array<number>(M).fill(0), ne = new Array<number>(M).fill(0), color = new Array<number>(N)let idx = 0function add(a: number, b: number): void {    e[idx] = b    ne[idx] = he[a]    he[a] = idx++}function dfs(u: number, cur: number): boolean {    color[u] = cur    for (let i = he[u]; i != -1; i = ne[i]) {        const j = e[i];        if (color[j] == cur) return false        if (color[j] == 0 && !dfs(j, 3 - cur)) return false    }    return true}function possibleBipartition(n: number, ds: number[][]): boolean {    he.fill(-1)    idx = 0    for (const info of ds) {        const a = info[0], b = info[1]        add(a, b); add(b, a)    }    color.fill(0)    for (let i = 1; i <= n; i++) {        if (color[i] != 0) continue        if (!dfs(i, 1)) return false    }    return true}

Python 代码:

class Solution:    def possibleBipartition(self, n: int, ds: List[List[int]]) -> bool:        N, M = 2010, 20010        he, e, ne, color = [-1] * N, [0] * M, [0] * M, [0] * N        idx = 0        def add(a, b):            nonlocal idx            e[idx], ne[idx], he[a] = b, he[a], idx            idx += 1        def dfs(u, cur):            color[u] = cur            i = he[u]            while i != -1:                j = e[i]                if color[j] == cur:                    return False                if color[j] == 0 and not dfs(j, 3 - cur):                    return False                i = ne[i]            return True        for info in ds:            a, b = info[0], info[1]            add(a, b)            add(b, a)        for i in range(1, n + 1):            if color[i] != 0:                continue            if not dfs(i, 1):                return False        return True
  • 工夫复杂度:$O(n + m)$
  • 空间复杂度:$O(n + m)$

反向点 + 并查集

咱们晓得对于 $ds[i] = (a, b)$ 而言,点 a 和点 b 必然位于不同的汇合中,同时因为只有两个候选汇合,因而这样的关系具备推断性:即对于 $(a, b)$ 和 $(b, c)$ 可知 ac 位于同一汇合。

因而,咱们能够对于每个点 x 而言,倡议一个反向点 x + n:若点 x 位于汇合 A 则其反向点 x + n 位于汇合 B,反之同理。

基于此,咱们能够应用「并查集」保护所有点的连通性:边保护变查看每个 $ds[i]$ 的联通关系,若 $ds[i] = (a, b)$ 联通,必然是其反向点联通所导致,必然是此前的其余 $ds[j]$ 导致的关系抵触,必然不能顺利划分成两个汇合,返回 false,否则返回 true

Java 代码:

class Solution {    int[] p = new int[4010];    int find(int x) {        if (p[x] != x) p[x] = find(p[x]);        return p[x];    }    void union(int a, int b) {        p[find(a)] = p[find(b)];    }    boolean query(int a, int b) {        return find(a) == find(b);    }    public boolean possibleBipartition(int n, int[][] ds) {        for (int i = 1; i <= 2 * n; i++) p[i] = i;        for (int[] info : ds) {            int a = info[0], b = info[1];            if (query(a, b)) return false;            union(a, b + n); union(b, a + n);        }        return true;    }}

TypeScript 代码:

const p = new Array<number>(4010).fill(0)function find(x: number): number {    if (p[x] != x) p[x] = find(p[x])    return p[x]}function union(a: number, b: number): void {    p[find(a)] = p[find(b)]}function query(a: number, b: number): boolean {    return find(a) == find(b)}function possibleBipartition(n: number, ds: number[][]): boolean {    for (let i = 1; i <= 2 * n; i++) p[i] = i    for (const info of ds) {        const a = info[0], b = info[1]        if (query(a, b)) return false        union(a, b + n); union(b, a + n)    }    return true}

Python 代码:

class Solution:    def possibleBipartition(self, n: int, ds: List[List[int]]) -> bool:        p = [i for i in range(0, 2 * n + 10)]        def find(x):            if p[x] != x:                p[x] = find(p[x])            return p[x]        def union(a, b):            p[find(a)] = p[find(b)]        def query(a, b):            return find(a) == find(b)        for info in ds:            a, b = info[0], info[1]            if query(a, b):                return False            else:                union(a, b + n)                union(b, a + n)        return True
  • 工夫复杂度:$O(n + m)$
  • 空间复杂度:$O(n)$

最初

这是咱们「刷穿 LeetCode」系列文章的第 No.886 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,局部是有锁题,咱们将先把所有不带锁的题目刷完。

在这个系列文章外面,除了解说解题思路以外,还会尽可能给出最为简洁的代码。如果波及通解还会相应的代码模板。

为了不便各位同学可能电脑上进行调试和提交代码,我建设了相干的仓库:https://github.com/SharingSou... 。

在仓库地址里,你能够看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其余优选题解。

更多更全更热门的「口试/面试」相干材料可拜访排版精美的 合集新基地

本文由mdnice多平台公布