【搜索】力扣547:省份数量

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

定义 省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例:

image
输入: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/
posted @ 2022-08-06 10:45  Vonos  阅读(68)  评论(0)    收藏  举报