Leetcode0886. 可能的二分法(medium,二分图)

目录

1. 问题描述

2. 解题分析

2.1 思路一

2.2 思路二

2.3 思路三:着色+深度优先搜索

3. 代码实现


1. 问题描述

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

给定整数 n 和数组 dislikes ,其中 dislikes[i] = [ai, bi] ,表示不允许将编号为 ai 和  bi的人归入同一组。当可以用这种方法将所有人分进两组时,返回 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
  • ai < bi
  • dislikes 中每一组都 不同

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/possible-bipartition
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

2. 解题分析

2.1 思路一

   dislikes代表一个图的edges列表表示,姑且叫这个图为dislike-graph。

        一个朴素的想法是,构建这个图的补图like-graph,然后检查该补图like-graph的连通子图个数是不是小于等于2。在补图like-graph中,两个不同连通子图中的节点显然是相互dislike的。因此如果like-graph的连通子图个数超过了2个,就无法分成2个小组,确保小组内没有相互dislike的人。而如果like-graph的连通子图个数等于2,那这两个子图中包含的节点集合就是满足题意的唯一的分组方式。如果like-graph的连通子图个数等于1(即为全联通图),那就可以任意划分了。

        当然,这个like-graph并不需要显式地创建。

        只需要创建dislike-graph的邻接列表(创建like-graph的邻接列表比创建dislike-graph的邻接列表要麻烦,存储需求大),然后基于dislike-graph的邻接列表进行like-graph的遍历即可,广度优先搜索或深度优先搜索均可。

        不过画了画图,从示例3的图发现以上这个想法并不正确。

2.2 思路二

        事实上这个问题等价于寻找这个dislike-graph中是否存在奇数个节点构成的环。

        (1) 存在奇数个节点构成的环的话,就无法把他们分成两组并避免不喜欢的人分到了一个组。这个是显而易见的。

        (2) 不存在奇数个节点构成的环的情况下,是不是就一定能够分成满足条件的两组呢?

        先假定以上这个思路成立吧(要证明的话好像还不是一件容易的事情)。

        从任何一个节点出发,对图进行路径遍历,如果某个路径上的新探索的节点已经存在于该路径上,就表示找到了一个环路。找到环路后检查该环路的长度即可。

        然后,重复以上过程,直到完成整个图的遍历。

        如果找到一个长度为奇数的环即可退出并返回False。如果没有找到任何长度为奇数的环则返回True。

        但是,针对某一条路径的搜索,是找到了一个环就退出呢,还是继续搜索呢?好像两者都行不通。

        呃。。。这条路好像走不通

        

2.3 思路三:着色+深度优先搜索

        尝试将每个人分配到两个组中的一个。假设第一组中的人是红色,第二组中的人是蓝色。

        如果第一个人是红色的,那么这个人不喜欢的人必须是蓝色的。然后,任何不喜欢蓝色人的人都是红色的,那么任何不喜欢红色人的人都是蓝色的,依此类推。如果在任何时候存在冲突,那么这个任务是不可能的完成的,因为从第一步开始每一步都符合逻辑。如果没有冲突,那么着色是有效的,说明分成两组是可行的。

        考虑由N个人构成的dislike-graph,dislikes数组表示改图的edge list。

        基于以上思路,我们需要检查该图的每个连通分支是否为二分图。

        对于每个连通的部分,我们只需试着用两种颜色对它进行着色,就可以检查它是否是二分的。可以按如下步骤进行深度优先搜索(本题的搜索与路径有关,不能用广度优先搜索):

  1.         将任一结点涂成红色,然后将它的所有邻居都涂成蓝色,然后将所有的邻居的邻居都涂成红色,以此类推。如果搜索过程发现某个准备涂成蓝色的节点之前已经被涂成红色(或者反过来)就表明产生冲突,分组无法成立,结束搜索。
  2.         以上遍历结束后,在从剩下未涂色的节点中任选一个开始新的遍历。
  3.         重复以上步骤2直到所有节点均已涂色(被访问),或者中途因发生冲突而提前退出。

        

3. 代码实现

import time
from typing import List
from collections import deque

class Solution:
    def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
        if n == 1:
            return True
        # construct adjacency list
        adjlist = [[] for k in range(n+1)]
        for dislike in dislikes:
            adjlist[dislike[0]].append(dislike[1])
            adjlist[dislike[1]].append(dislike[0])
            
        color = (n+1) * [-1]
        for k in range(1,n+1):
            if color[k] < 0:
                q = deque([k])
                color[k] = 0
                while len(q)>0:
                    node = q.pop()
                    for adj in adjlist[node]:
                        if color[adj] < 0:
                            color[adj] = 1 - color[node]
                            q.append(adj)
                        else:
                            if color[adj] == color[node]:
                                return False
        return True
                    
if __name__ == "__main__":
    
    sln = Solution()
    
    n = 4
    dislikes = [[1,2],[1,3],[2,4]]
    print(sln.possibleBipartition(n, dislikes))
    
    n = 3
    dislikes = [[1,2],[1,3],[2,3]]
    print(sln.possibleBipartition(n, dislikes))
    
    n = 5
    dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]
    print(sln.possibleBipartition(n, dislikes))

        执行用时:108 ms, 在所有 Python3 提交中击败了91.64%的用户

        内存消耗:18.4 MB, 在所有 Python3 提交中击败了89.10%的用户

         嗯,能够在时间和内存两方面都表现不错,还是值得小小得意一下。

        吐个小槽:n=1的时候按leetcode的反馈应该返回True,但是这其实是个定义的问题。leetcode拿这个当作一个testcase有些无聊了。不如直接定义n>=2不是更清爽嘛。

        回到总目录: 笨牛慢耕的Leetcode每日一题总目录(动态更新。。。)

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值