青云算法面试题干货-把人分成两组-LeetCode第886题

题目:有N个人,编号从1到N。现在我们试图把他们分成两组。一个人可能不喜欢某些人,那么这个人和他不喜欢的人不能分到一组。输入一个数组dislikes,数组的第i项dislikes[i] = [a, b]表示编号为a的人不喜欢编号为b的人,因此他们不能分到同一组。请根据dislikes关系判断输入的N个人能否分到两个组里?

分析:这是LeetCode第886题。

要想解决这个问题,首先我们要发现这个题目是关于图的问题。每个人是图中的一个节点。如果一个人a不喜欢另一个人b,那么a与b之间有一条边相连。在这个题目中a不喜欢b意味着a、b不能分到同一组。不管b是否不喜欢a,只要a不喜欢b他们就不能放在同一组里。因此这个图是无向图。如果看成有向图,只要从a到b有一条边,那么从b到a也有一条边。

如果这些人能够分成两组,我们可以想象这些人在图中对应的节点能够用两种颜色标记。分到第一组的人用颜色c1标记,另一组的人用颜色c2标记。接着我们考虑怎么给图中每个节点标记颜色。

最开始的时候每个节点都没有标记颜色。我们逐个扫描图中的节点,如果发现一个节点p没有被标记过颜色,那么给他标记颜色c1。接着我们看这个节点p都有哪些节点和它相邻,如果相邻的节点q之前没有标记过颜色,我们给它们标记成c2。这是因为p和q相邻,表示他们之间相互不喜欢,不能分在同一组,因此不能用相同的颜色标记。如果节点q之前已经被标记过颜色了,那么看之前标记的是什么颜色。如果之前标记的是c2,那么没有必要再重复标记了。如果之前标记的颜色是c1,那么出现冲突了,一个人不能分到两个组里,因此我们不能按照这个分组规则把所有人分成两组。

接下来我们再从节点q出发去,把和它相邻的节点标记颜色c1,再以此类推。这就是一个深度优先搜索的过程。下面是基于图的深度优先搜索的参考代码:

public boolean possibleBipartition(int N, int[][] dislikes) {
    int[] colors = new int[N + 1];
    Arrays.fill(colors, -1);

    Map<Integer, List<Integer>> graph = new HashMap<>();
    for (int[] pair : dislikes) {
        if (!graph.containsKey(pair[0])) {
            graph.put(pair[0], new LinkedList<Integer>());
        }

        if (!graph.containsKey(pair[1])) {
            graph.put(pair[1], new LinkedList<Integer>());
        }

        graph.get(pair[0]).add(pair[1]);
        graph.get(pair[1]).add(pair[0]);
    }

    for (int i = 1; i <= N; ++i) {
        if (colors[i] == -1 && !dfs(graph, i, colors, 0)) {
            return false;
        }
    }

    return true;
}

在上述代码中,先根据dislikes的关系构建一个无向图graph。图中的每个节点有三种可能的状态表示颜色。如果一个节点之前没有被标记过颜色,它的值为-1。如果给它标记颜色,颜色有两种可能的值,0和1。如果一个节点的颜色是c,那么和它相邻的颜色的值应该是1-c。

下面是按照深度优先搜索的顺序给编号为i的节点以及和它相邻的节点标记颜色的代码:

private boolean dfs(Map<Integer, List<Integer>> graph,
                    int i, int[] colors, int color) {
    if (colors[i] >= 0) {
        return colors[i] == color;
    }

    colors[i] = color;
    for (int neighbor : graph.getOrDefault(i, new LinkedList<Integer>())) {
        if (!dfs(graph, neighbor, colors, 1 - color)) {
            return false;
        }
    }

    return true;
}

图的深度优先搜索的时间复杂度是O(V+E),其中V是图中节点的数目,而E是图中边的数目。

举一反三:这个题和LeetCode第785题是同一类型的题目:给定一个无向图,请问能否把图中的所有节点分成A、B两组,使得每条边的一个节点在A组,另一个节点在B组?前面的题目给出的是一个具体的应用场景,而这个题目直接给出了应用场景背后的模型,实质上是同一个问题,可以用同样的方法解决。

更多算法面试题的讨论,欢迎访问博客http://qingyun.io/blogs。

猜你喜欢

转载自blog.csdn.net/QingyunAlgo/article/details/82106963