LeetCode 547 Medium 朋友圈 並查集 多種解法 Python
方法一:
演算法:並查集
思路:
本題有很強的並查集題目的特徵,使用並查集資料結構可以比較直觀地解題,先看並查集如何解題:
遍歷鄰接矩陣M,如果M[i][j]==1即二者是朋友,那麼合併i,j集合,遍歷完整個矩陣M後則剩餘
的集合數量就是有多少個朋友圈
--------------------------------------------------------------------------------------------------------------------------------
並查集:
直觀來講並查集就是將N個元素,同一類元素劃分到一個集合中,集合是有標識的,對某個元素p的集合標識,
就是id=set_id(p)
並查集中主要的操作有兩個,find和union,查詢與合併
查詢find很好說,就是return set_id(p)
合併union就是求集合的並,比如標識為id_P和id_Q的兩個集合合併,就是將標識是id_P的所有元素,或者將
id_P內的所有元素的標識id改成id_Q,這樣原來id_P內的所有M個元素和id_Q內的所有N個元素合併,新的集合標識
是id_Q(或者id_P),元素個數是M+N
以列表元素構建並查集為例
初始set_id=[i for i in range(n)],每個元素的標識由下標來指定
find(p) : return set_id[p]
union(p,q):
如果find(p) == find(q),那沒啥事,return,也不用合併
否則,遍歷整個set_id,將set_id = p的變成q,(或者q的變成p)
以此構造是一種比較直觀的線性表下的並查集,其複雜度為ON,因為要遍歷所有set_id嘛
-------------------------------------------------------------------------------------------------------------------------------------
基於線性的並查集,可以構建效率更高的並查集森林
並查集森林將並查集節點構建成樹狀,合併和查詢的效率更高,根節點作為set_id標識,如下圖,元素2,1,0,3的並查集標識id就是2,元素4和5的set_id就是4,
即find(2) = 2,find(1) = 2,...,find(4) = 4,find(5) = 5,二者Union後就是右邊這樣,整個樹標識為2,這些元素同屬 一個並查集
2 4 2
/ \ / /| \
1 3 Union 5 --> 1 3 4
/ / \
0 0 5
class DisjointSet:
def __init__(self,n):
self.set_id = [i for i in range(n)]
self.set_size = [1 for _ in range(n)]
self.count = n
def find(self,p):
while p !=self.set_id[p]:
self.set_id[p] = self.set_id[self.set_id[p]]
p = self.set_id[p]
return p
def union(self,p,q):
i = self.find(p)
j = self.find(q)
if i == j:
return
if self.set_size[i] < self.set_size[j]:
self.set_id[i] = j
self.set_size[j] += self.set_size[i]
else:
self.set_id[j] = i
self.set_size[i] += self.set_size[j]
self.count -= 1
class Solution:
def findCircleNum(self, M):
"""
:type M: List[List[int]]
:rtype: int
"""
disjoint_set = DisjointSet(len(M))
for i in range(len(M)):
for j in range(len(M)):
if M[i][j] == 1 :
disjoint_set.union(i,j)
return disjoint_set.count
方法二:
DFS/BFS遍歷圖
def findCircleNum1(self, M):
"""
演算法:DFS
思路:
可以將題目轉換為是在一個圖中求連通子圖的問題,給出的N*N的矩陣就是鄰接矩陣,建立N個節點的visited陣列,
從not visited的節點開始深度優先遍歷,遍歷就是在鄰接矩陣中去遍歷,如果在第i個節點的鄰接矩陣那一行中的第j
個位置處M[i][j]==1 and not visited[j],就應該dfs到這個第j個節點的位置,
複雜度分析:
時間:ON2?遍歷所有節點
空間:ON,visited陣列
"""
if M == [] or M[0] == []:
return 0
n = len(M)
visited = [False] * n
def dfs(i):
visited[i] = True
for j in range(n):
if M[i][j] == 1 and not visited[j]:
dfs(j)
counter = 0
for i in range(n):
if not visited[i]:
dfs(i)
counter += 1
return counter
def findCircleNum2( M):
"""
和上面相近,這裡用BFS去遍歷
"""
if M == [] or M[0] == []:
return 0
n = len(M)
visited = [False] * n
counter = 0
for i in range(n):
if not visited[i]:
queue = [i]
while queue:
index = queue.pop(0)
visited[index] = True
for j in range(n):
if M[index][j] == 1 and not visited[j]:
queue.append(j)
counter += 1
return counter