【搜索】力扣547:省份数量
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
定义 省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
按题目的“省份”可能不太好理解,那么可以将一个省份看作一个朋友圈,相连的朋友为一个朋友圈,没有相连的朋友的话自己就是一个朋友圈。所以若有 n 个人,最多有 n 个朋友圈;多一条连起来的边就少一个朋友圈,至少有一个朋友圈,因此 isConnected ∈ [1, n]。
可以把 n 个城市和它们之间的相连关系看成图,城市是图中的结点,相连关系是图中的边,给定的矩阵 isConnected 即为图的邻接矩阵,省份即为图中的连通分量。
计算省份总数,等价于计算图中的连通分量数,可以通过深度优先搜索或广度优先搜索实现,也可以通过并查集实现。
深度优先搜索
深度优先搜索的思路是很直观的。
遍历所有城市,对于每个城市,如果该城市尚未被访问过(not in visited),则从该城市开始深度优先搜索,通过矩阵 isConnected 得到与该城市直接相连的城市有哪些,这些城市和该城市属于同一个连通分量,然后对这些城市继续深度优先搜索,直到同一个连通分量的所有城市都被访问到,即可得到一个省份。遍历完全部城市以后,即可得到连通分量的总数,即省份的总数。
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
def dfs(i):
for j in range(cities): # 在一维数组中搜索
if isConnected[i][j] == 1 and j not in visited:
visited.add(j)
dfs(j)
cities = len(isConnected) # 城市数量
visited = set() # 记录每个城市是否被访问过
provinces = 0 # 构成的省份数量,初始化为 0
for i in range(cities):
if i not in visited:
dfs(i)
provinces += 1
return provinces
时间复杂度:O(n^2),其中 n 是城市的数量。需要遍历矩阵 n 中的每个元素。
空间复杂度:O(n)。需要使用数组 visited 记录每个城市是否被访问过,数组长度是 n,递归调用栈的深度不会超过 n。
广度优先搜索
对于每个城市,如果该城市尚未被访问过,则从该城市开始广度优先搜索,直到同一个连通分量中的所有城市都被访问到,即可得到一个省份。
from collections import deque
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
cities = len(isConnected) # 城市数量
visited = set() # 记录每个城市是否被访问过
provinces = 0 # 构成的省份数量,初始化为 0
for i in range(cities):
if i not in visited: # 没有被访问,就将这个一维数组加入队列,搜索其中连通的城市
queue = collections.deque([i]) # 注意加入的是数组
while queue:
j = queue.popleft()
visited.add(j)
for k in range(cities):
if isConnected[j][k] == 1 and k not in visited:
queue.append(k)
provinces += 1
return provinces
时间复杂度:O(n^2),其中 n 是城市的数量。需要遍历矩阵 isConnected 中的每个元素。
空间复杂度:O(n),其中 n 是城市的数量。需要使用数组 visited 记录每个城市是否被访问过,数组长度是 n,广度优先搜索使用的队列的元素个数不会超过 n。
并查集
计算连通分量数的另一个方法是使用并查集。
初始时,每个城市都属于不同的连通分量。遍历矩阵 isConnected,如果两个城市之间有相连关系,则它们属于同一个连通分量,对它们进行合并。
遍历矩阵 isConnected 的全部元素之后,计算连通分量的总数,即为省份的总数。
(不想学了,直接复制代码)
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
def find(index: int) -> int:
if parent[index] != index:
parent[index] = find(parent[index])
return parent[index]
def union(index1: int, index2: int):
parent[find(index1)] = find(index2)
cities = len(isConnected)
parent = list(range(cities))
for i in range(cities):
for j in range(i + 1, cities):
if isConnected[i][j] == 1:
union(i, j)
provinces = sum(parent[i] == i for i in range(cities))
return provinces
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/number-of-provinces/solution/sheng-fen-shu-liang-by-leetcode-solution-eyk0/
时间复杂度:O(n2logn),其中 n 是城市的数量。需要遍历矩阵 isConnected 中的所有元素,时间复杂度是 O(n^2),如果遇到相连关系,则需要进行 2 次查找和最多 1 次合并,一共需要进行 2n^2 次查找和最多 n^2 次合并,因此总时间复杂度是 O(2n^2 log n^2) = O(n^2 log n)。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 O(n^2 log n),平均情况下的时间复杂度依然是 O(n^2 α(n)),其中 α 为阿克曼函数的反函数,α(n) 可以认为是一个很小的常数。
空间复杂度:O(n。需要使用数组 parent 记录每个城市所属的连通分量的祖先。
并查集模板
class UnionFind:
def __init__(self):
"""
记录每个节点的父节点
"""
self.father = {}
def find(self,x):
"""
查找根节点
路径压缩
"""
root = x
while self.father[root] != None:
root = self.father[root]
# 路径压缩
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
def merge(self,x,y,val):
"""
合并两个节点
"""
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
def is_connected(self,x,y):
"""
判断两节点是否相连
"""
return self.find(x) == self.find(y)
def add(self,x):
"""
添加新节点
"""
if x not in self.father:
self.father[x] = None
作者:musiala
链接:https://leetcode.cn/problems/number-of-provinces/solution/python-duo-tu-xiang-jie-bing-cha-ji-by-m-vjdr/


浙公网安备 33010602011771号