搜索题目:可能的二分法

文章目录

题目

标题和出处

标题:可能的二分法

出处:886. 可能的二分法

难度

5 级

题目描述

要求

需要将一组 n \texttt{n} n 个人(编号从 1 \texttt{1} 1 到 n \texttt{n} n)分成任意大小的两组。每个人可能不喜欢其他的一些人,他们不应该属于同一组。

给定整数 n \texttt{n} n 和数组 dislikes \texttt{dislikes} dislikes,其中 dislikes[i] = [a i , b i ] \texttt{dislikes[i] = [a}\texttt{i}\texttt{, b}\texttt{i}\texttt{]} dislikes[i] = [ai, bi],表示编号为 a i \texttt{a}\texttt{i} ai 的人不喜欢编号为 b i \texttt{b}\texttt{i} bi 的人,如果可以用这种方法将所有人分成两组,返回 true \texttt{true} true。

示例

示例 1:

输入: n = 4, dislikes = [[1,2],[1,3],[2,4]] \texttt{n = 4, dislikes = [[1,2],[1,3],[2,4]]} n = 4, dislikes = [[1,2],[1,3],[2,4]]

输出: true \texttt{true} true

解释:第一组是 [1,4] \texttt{[1,4]} [1,4],第二组是 [2,3] \texttt{[2,3]} [2,3]。

示例 2:

输入: n = 3, dislikes = [[1,2],[1,3],[2,3]] \texttt{n = 3, dislikes = [[1,2],[1,3],[2,3]]} n = 3, dislikes = [[1,2],[1,3],[2,3]]

输出: false \texttt{false} false

示例 3:

输入: n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]] \texttt{n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]} n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]

输出: false \texttt{false} false

数据范围

  • 1 ≤ n ≤ 2000 \texttt{1} \le \texttt{n} \le \texttt{2000} 1≤n≤2000
  • 0 ≤ dislikes.length ≤ 10 4 \texttt{0} \le \texttt{dislikes.length} \le \texttt{10}^\texttt{4} 0≤dislikes.length≤104
  • dislikes[i].length = 2 \texttt{dislikes[i].length} = \texttt{2} dislikes[i].length=2
  • 1 ≤ dislikes[i][j] ≤ n \texttt{1} \le \texttt{dislikes[i][j]} \le \texttt{n} 1≤dislikes[i][j]≤n
  • a i < b i \texttt{a}\texttt{i} < \texttt{b}\texttt{i} ai<bi
  • dislikes \texttt{dislikes} dislikes 中的所有组各不相同

前言

将 n n n 个人和他们之间的不喜欢关系看成无向图,每个人是一个结点,每个不喜欢关系是一条无向边。如果可以按照题目要求分组,则同一条边连接的两个结点属于不同的组,等价于无向图是二分图。因此,判断是否可以按照题目要求将 n n n 个人分成两组,等价于判断无向图是否为二分图。

解法一

思路和算法

根据二分图的定义,图中任意一条边连接的两个结点属于不同的组。

以下将两个组分别称为 A \text{A} A 组和 B \text{B} B 组。为了判断是否可以按照题目要求将 n n n 个人分成两组,需要将每个人对应的结点着色,将 A \text{A} A 组与 B \text{B} B 组的结点颜色分别称为 A \text{A} A 颜色与 B \text{B} B 颜色。

可以使用广度优先搜索判断无向图是否为二分图,搜索过程中将图中的每个结点着色。

由于题目中的图的表示方式是边数组,为了方便处理,需要首先将边数组转换成邻接结点列表的形式,转换后可以在 O ( 1 ) O(1) O(1) 时间获得一个结点的全部相邻结点,然后使用广度优先搜索遍历图。

遍历 n n n 个结点,如果遇到结点 i i i 未着色,则结点 i i i 所在的连通分量中的全部结点都未着色,将结点 i i i 的颜色设为 A \text{A} A 颜色,从结点 i i i 开始遍历其所在的连通分量中的全部结点,将每个结点着色并判断是否为二分图。

对于访问到的每个结点 node \textit{node} node,该结点一定已经着色,用 color \textit{color} color 表示结点 node \textit{node} node 的颜色,遍历与 next \textit{next} next 相邻的每个结点,执行如下操作。

  • 如果相邻结点未着色,则将相邻结点的颜色设为与 color \textit{color} color 不同的另一种颜色,继续访问相邻结点。

  • 如果相邻结点的颜色是 color \textit{color} color,则存在两个相邻结点的颜色相同,因此无向图不是二分图,返回 false \text{false} false。

如果遍历结束之后没有出现不符合二分图的着色情况,则无向图是二分图,返回 true \text{true} true。

上述做法的正确性说明如下。

着色的过程中,每个连通分量的首个着色结点的颜色是 A \text{A} A 颜色,对于其余每个结点,当该结点着色时,一定存在一个相邻结点的颜色与当前结点的颜色相反。如果遇到两个相邻结点的颜色相同,则任何着色方案都不可能使任意两个相邻结点的颜色不同,因此无向图不是二分图。

代码

java 复制代码
class Solution {
    static final int UNKNOWN = 0;
    static final int COLOR_A = 1;
    static final int COLOR_B = 2;

    public boolean possibleBipartition(int n, int[][] dislikes) {
        List<Integer>[] adjacentArr = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            adjacentArr[i] = new ArrayList<Integer>();
        }
        for (int[] edge : dislikes) {
            adjacentArr[edge[0]].add(edge[1]);
            adjacentArr[edge[1]].add(edge[0]);
        }
        int[] colors = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            if (colors[i] != UNKNOWN) {
                continue;
            }
            colors[i] = COLOR_A;
            Queue<Integer> queue = new ArrayDeque<Integer>();
            queue.offer(i);
            while (!queue.isEmpty()) {
                int node = queue.poll();
                int color = colors[node];
                int nextColor = color == COLOR_A ? COLOR_B : COLOR_A;
                List<Integer> adjacent = adjacentArr[node];
                for (int next : adjacent) {
                    if (colors[next] == UNKNOWN) {
                        colors[next] = nextColor;
                        queue.offer(next);
                    } else if (colors[next] == color) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n n n 是图中的结点数, m m m 是图中的边数。广度优先搜索的时间复杂度由结点数和边数决定。

  • 空间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n n n 是图中的结点数, m m m 是图中的边数。邻接结点列表需要 O ( n + m ) O(n + m) O(n+m) 的空间,颜色数组和队列需要 O ( n ) O(n) O(n) 的空间。

解法二

思路和算法

也可以使用深度优先搜索判断无向图是否为二分图,搜索过程中将图中的每个结点着色。

由于题目中的图的表示方式是边数组,为了方便处理,需要首先将边数组转换成邻接结点列表的形式,转换后可以在 O ( 1 ) O(1) O(1) 时间获得一个结点的全部相邻结点,然后使用深度优先搜索遍历图。

遍历 n n n 个结点,如果遇到结点 i i i 未着色,则结点 i i i 所在的连通分量中的全部结点都未着色,将结点 i i i 的颜色设为 A \text{A} A 颜色,从结点 i i i 开始遍历其所在的连通分量中的全部结点,将每个结点着色并判断是否为二分图。

对于访问到的每个结点 node \textit{node} node,该结点一定已经着色,用 color \textit{color} color 表示结点 node \textit{node} node 的颜色,遍历与 next \textit{next} next 相邻的每个结点,执行如下操作。

  • 如果相邻结点未着色,则将相邻结点的颜色设为与 color \textit{color} color 不同的另一种颜色,继续访问相邻结点。

  • 如果相邻结点的颜色是 color \textit{color} color,则存在两个相邻结点的颜色相同,因此无向图不是二分图,返回 false \text{false} false。

如果遍历结束之后没有出现不符合二分图的着色情况,则无向图是二分图,返回 true \text{true} true。

上述做法的正确性说明如下。

着色的过程中,每个连通分量的首个着色结点的颜色是 A \text{A} A 颜色,对于其余每个结点,当该结点着色时,一定存在一个相邻结点的颜色与当前结点的颜色相反。如果遇到两个相邻结点的颜色相同,则任何着色方案都不可能使任意两个相邻结点的颜色不同,因此无向图不是二分图。

代码

java 复制代码
class Solution {
    static final int UNKNOWN = 0;
    static final int COLOR_A = 1;
    static final int COLOR_B = 2;
    List<Integer>[] adjacentArr;
    int[] colors;

    public boolean possibleBipartition(int n, int[][] dislikes) {
        adjacentArr = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            adjacentArr[i] = new ArrayList<Integer>();
        }
        for (int[] edge : dislikes) {
            adjacentArr[edge[0]].add(edge[1]);
            adjacentArr[edge[1]].add(edge[0]);
        }
        this.colors = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            if (colors[i] == UNKNOWN) {
                if (!dfs(i, COLOR_A)) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean dfs(int node, int color) {
        colors[node] = color;
        boolean valid = true;
        int nextColor = color == COLOR_A ? COLOR_B : COLOR_A;
        List<Integer> adjacent = adjacentArr[node];
        for (int next : adjacent) {
            if (colors[next] == UNKNOWN) {
                valid = dfs(next, nextColor);
            } else if (colors[next] == color) {
                valid = false;
            }
            if (!valid) {
                return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n n n 是图中的结点数, m m m 是图中的边数。深度优先搜索的时间复杂度由结点数和边数决定。

  • 空间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n n n 是图中的结点数, m m m 是图中的边数。邻接结点列表需要 O ( n + m ) O(n + m) O(n+m) 的空间,颜色数组和递归调用栈需要 O ( n ) O(n) O(n) 的空间。

解法三

预备知识

该解法涉及到并查集。

并查集是一种树型的数据结构,用于处理不相交集合的合并与查询问题。

思路和算法

判断无向图是否为二分图,可以将属于同一个集合的结点合并,然后遍历每一条边,根据同一条边连接的两个结点所属的集合判断无向图是否为二分图。

由于题目中的图的表示方式是边数组,为了方便处理,需要首先将边数组转换成邻接结点列表的形式,转换后可以在 O ( 1 ) O(1) O(1) 时间获得一个结点的全部相邻结点,然后使用并查集判断无向图是否为二分图。

并查集初始化时, n n n 个结点分别属于 n n n 个不同的集合,每个集合只包含一个结点。

初始化之后,执行合并操作。对于结点 i i i, graph [ i ] \textit{graph}[i] graph[i] 中的所有结点都和结点 i i i 属于不同的集合,因此 graph [ i ] \textit{graph}[i] graph[i] 中的所有结点属于同一个集合,将这些结点所在的集合做合并。

合并操作结束之后,遍历每一条边,执行如下判断。

  • 如果存在同一条边连接的两个结点属于同一个集合,则无向图不是二分图,返回 false \text{false} false。

  • 如果每一条边连接的两个结点都属于不同集合,则无向图是二分图,返回 true \text{true} true。

代码

java 复制代码
class Solution {
    public boolean possibleBipartition(int n, int[][] dislikes) {
        List<Integer>[] adjacentArr = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            adjacentArr[i] = new ArrayList<Integer>();
        }
        for (int[] edge : dislikes) {
            adjacentArr[edge[0]].add(edge[1]);
            adjacentArr[edge[1]].add(edge[0]);
        }
        UnionFind uf = new UnionFind(n + 1);
        for (int i = 1; i <= n; i++) {
            List<Integer> adjacent = adjacentArr[i];
            int count = adjacent.size();
            for (int j = 1; j < count; j++) {
                uf.union(adjacent.get(0), adjacent.get(j));
            }
        }
        for (int i = 1; i <= n; i++) {
            int root = uf.find(i);
            List<Integer> adjacent = adjacentArr[i];
            for (int j : adjacent) {
                if (uf.find(j) == root) {
                    return false;
                }
            }
        }
        return true;
    }
}

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        rank = new int[n];
    }

    public void union(int x, int y) {
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else if (rank[rootx] < rank[rooty]) {
                parent[rootx] = rooty;
            } else {
                parent[rooty] = rootx;
                rank[rootx]++;
            }
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
}

复杂度分析

  • 时间复杂度: O ( n + m × α ( m ) ) O(n + m \times \alpha(m)) O(n+m×α(m)),其中 n n n 是图中的结点数, m m m 是图中的边数, α \alpha α 是反阿克曼函数。生成邻接结点列表需要 O ( n + m ) O(n + m) O(n+m) 的时间,并查集的初始化需要 O ( n ) O(n) O(n) 的时间,然后遍历每条边执行 O ( m ) O(m) O(m) 次合并操作,最后遍历每条边判断是否为二分图,因此并查集初始化之后的操作的时间复杂度是 O ( m × α ( m ) ) O(m \times \alpha(m)) O(m×α(m)),总时间复杂度是 O ( n + m × α ( m ) ) O(n + m \times \alpha(m)) O(n+m×α(m))。

  • 空间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n n n 是图中的结点数, m m m 是图中的边数。邻接结点列表需要 O ( n + m ) O(n + m) O(n+m) 的空间,并查集需要 O ( n ) O(n) O(n) 的空间。

相关推荐
伟大的车尔尼4 天前
搜索题目:边界着色
深度优先搜索·广度优先搜索
不知名的忻9 天前
并查集(QuickUnion)
java·数据结构·算法·并查集
伟大的车尔尼10 天前
搜索题目:二进制矩阵中的最短路径
广度优先搜索
伟大的车尔尼12 天前
搜索题目:被围绕的区域
并查集·深度优先搜索·广度优先搜索
Tisfy13 天前
LeetCode 1722.执行交换操作后的最小汉明距离:连通图
算法·leetcode·dfs·题解·深度优先搜索·连通图
伟大的车尔尼17 天前
搜索题目:地图分析
动态规划·广度优先搜索
伟大的车尔尼19 天前
搜索题目:腐烂的橘子
广度优先搜索
伟大的车尔尼19 天前
搜索题目:01 矩阵
动态规划·广度优先搜索
伟大的车尔尼24 天前
搜索题目:图像渲染
并查集·深度优先搜索·广度优先搜索