跟着专注于计算机视觉的AndyJ的妈妈我学算法之每日一题leetcode547朋友圈-并查集解法

这个题,可以有三种解法。BFS,DFS,Union-Find。
BFS,DFS,可以理解为朋友圈是多个图/树,然后看有多少个独立的子图/树。
本文讲Union-Find解法,及其优化。下文讲BFS,DFS。
并查集中,有一个点要注意
如果两个node,有connection,但是father node不同,这时候要把father node进行连接。
注意,两个father node的的father node,其实是他自己。
所以,要将一个father node的father,连到另一个father node上!
这里第一次写出了错,还是并查集没有理解到位,要注意!

好了,这篇博客是希望你有一定的Union-Find的基础。
题目:

547. 朋友圈
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

示例 1:

输入: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
输出: 2 
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:

输入: 
[[1,1,0],
 [1,1,1],
 [0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:

N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。

我发现,再多的解释也不如code有力。自己有时候刷题看解释总是看不懂,仔细研读code,然后一下就明白了,code最有逻辑,不会出错。所以,上code:

class Solution:
    def find(self, node):
        while node != self.father[node]:
            node = self.father[node]
        return node


    def connect(self, p1, p2):
        father1 = self.find(p1)
        father2 = self.find(p2)
        if father1 != father2:
            self.father[father1] = father2
            self.cycle -= 1

    def findCircleNum(self, M: List[List[int]]) -> int:
        n = len(M)
        self.cycle = n
        self.father = list(range(n))

        for i in range(n):
            for j in range(i+1, n):
                if M[i][j]==1:
                    self.connect(i,j)
        return self.cycle
        
# Your runtime beats 17.8 % of python3 submissions
# Your memory usage beats 11.11 % of python3 submissions (13.9 MB)

总的来说,其实就是维护了每个node的father,father的father,。。。不断的father(即:代码里的self.father)

其实还有优化的空间:

  1. 平衡性优化:小的树接到大的树下面,体现在代码里是connect的时候,要判断数的大小。所以采用一个额外的数组记录数的大小。
  2. 路径压缩:对于一个子树来说,不以为他是一长串,每次找father的时候,都要一个一个往上,复杂度是O(n)。我们希望他是并排铺开的,这样找father就是O(1)。体现在代码里是find的时候,做一个路径压缩。实现压缩每棵树的高度。

这里说的不清楚的话,可以看下这个题解

好了,先做优化1。实现小树接大树。
code:

class Solution:
    def find(self, node):
        while node != self.father[node]:
            node = self.father[node]
        return node


    def connect(self, p1, p2):
        father1 = self.find(p1)
        father2 = self.find(p2)
        if father1 != father2:
            if self.size[father1] > self.size[father2]: # father1树比father2数的大,所以2接1.
                self.father[father2] = father1 # 把2的father变成1的father
                self.size[father1] += self.size[father2] # father1膨胀了
            else: # 情况相反
                self.father[father1] = father2
                self.size[father2] += self.size[father1]
            self.cycle -= 1

    def findCircleNum(self, M: List[List[int]]) -> int:
        n = len(M)
        self.cycle = n
        self.father = list(range(n))
        self.size = [1 for _ in range(n)]

        for i in range(n):
            for j in range(i+1, n):
                if M[i][j]==1:
                    self.connect(i,j)
        return self.cycle

# Your runtime beats 82.96 % of python3 submissions
# Your memory usage beats 11.11 % of python3 submissions 

然后在优化1的基础上,做优化2。压缩每棵树的高度。实现找father是复杂度的降低。
code:

class Solution:
    def find(self, node):
        while node != self.father[node]:
            # 找他爸太花时间,因为他爸还要找他爸,所以直接找他爷爷。
            # 把他爷爷当作他爸,来缩小树的高度。
            self.father[node] = self.father[self.father[node]]

            # 接着while
            node = self.father[node]
        return node


    def connect(self, p1, p2):
        father1 = self.find(p1)
        father2 = self.find(p2)
        if father1 != father2:
            if self.size[father1] > self.size[father2]: # father1树比father2数的大,所以2接1.
                self.father[father2] = father1 # 把2的father变成1的father
                self.size[father1] += self.size[father2] # father1膨胀了
            else: # 情况相反
                self.father[father1] = father2
                self.size[father2] += self.size[father1]
            self.cycle -= 1

    def findCircleNum(self, M: List[List[int]]) -> int:
        n = len(M)
        self.cycle = n
        self.father = list(range(n))
        self.size = [1 for _ in range(n)]

        for i in range(n):
            for j in range(i+1, n):
                if M[i][j]==1:
                    self.connect(i,j)
        return self.cycle

# Your runtime beats 82.96 % of python3 submissions
# Your memory usage beats 11.11 % of python3 submissions

不过,这个提交的时间,说明不了什么。这个和服务器当前的运行状态关系比较大。没啥用。。。
我们应该从算法本身去降低时间/空间复杂度。这应该是优化的初衷。
好了,今天就到这里了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值