zoukankan      html  css  js  c++  java
  • Leetcode-并查集

    200.岛屿数量 https://leetcode-cn.com/problems/number-of-islands/

    给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

    示例:

    输入:
    11000
    11000
    00100
    00011
    
    输出: 3

    解:

    1. 转换为染色问题:

    遍历所有节点

      if node ==1:

        count ++; 将node附近陆地节点赋0

      else:

        continue

    问题,将node附近陆地节点赋0,如何实现? dfs,bfs

    dfs 实现,递归自然的提供栈

    class Solution:
        def numIslands(self, grid: List[List[str]]) -> int:
            if not grid or not grid[0]:
                return 0
            self.dx = [-1, 1, 0, 0]
            self.dy = [0, 0, -1, 1]  # 便于向四周扩散,先定义好(dx, dy),左、右、下、上
            
            self.max_x, self.max_y = len(grid), len(grid[0])  # 两个边界
            
            self.grid = grid  # 便于传参
            self.visited = set()   # 不污染原数组,用一个set做遍历标记
            
            res = []
            for i in range(self.max_x):  # 遍历所有节点,如果需要扩散染色就append 1,否则append 0
                for j in range(self.max_y):
                    res.append(self.floodfill_dfs(i,j))
                    
            return sum(res)  # sum后即为所求
        
        def floodfill_dfs(self, x, y):
            if not self.is_valid(x, y):  # 如果(x, y)不是需要扩散染色的陆地,直接返回0
                return 0
            self.visited.add((x, y))
            for k in range(4):  # 左、右、下、上4个方向扩展遍历
                self.floodfill_dfs(x + self.dx[k], y + self.dy[k])
            return 1
        
        def is_valid(self, x, y):
            """判断(x, y)是否为需要扩散染色的陆地"""
            if x < 0 or x >= self.max_x or y < 0 or y >= self.max_y:  # 越界的位置全是水
                return False
            if self.grid[x][y] == '0' or ((x, y) in self.visited):  # ‘0’ node 和 已经染色过的‘1’ node,都不需要染色,不破坏原数组grid
                return False
            return True
    

      

    bfs实现,利用队列

    import collections
    class Solution:
        def numIslands(self, grid: List[List[str]]) -> int:
            if not grid or not grid[0]:
                return 0
            self.dx = [-1, 1, 0, 0]
            self.dy = [0, 0, -1, 1]  # 便于向四周扩散,先定义好(dx, dy),左、右、下、上
            
            self.max_x, self.max_y = len(grid), len(grid[0])  # 两个边界
            
            self.grid = grid  # 便于传参
            self.visited = set()   # 不污染原数组,用一个set做遍历标记
            
            res = []
            for i in range(self.max_x):  # 遍历所有节点,如果需要扩散染色就append 1,否则append 0
                for j in range(self.max_y):
                    res.append(self.floodfill_bfs(i,j))
                    
            return sum(res)  # sum后即为所求
        
        def floodfill_bfs(self, x, y):
            if not self.is_valid(x, y):  # 如果(x, y)不是需要扩散染色的陆地,直接返回0
                return 0
            
            self.visited.add((x, y))
            queue = collections.deque()  # bfs用队列实现
            queue.append((x,y))  # 先入队遍历的起始点
            
            while queue:
                cur_x, cur_y = queue.popleft()
                for k in range(4):  # 所有和当前pop出来的节点相邻的点都要染色(前提是如果需要的话)
                    new_x, new_y = cur_x+self.dx[k], cur_y+self.dy[k]
                    if self.is_valid(new_x, new_y):
                        self.visited.add((new_x, new_y))  # 相当于染色(new_x, new_y)
                        queue.append((new_x, new_y))  # 相邻节点入队
            return 1
        
        def is_valid(self, x, y):
            """判断(x, y)是否为需要扩散染色的陆地"""
            if x < 0 or x >= self.max_x or y < 0 or y >= self.max_y:  # 越界的位置全是水
                return False
            if self.grid[x][y] == '0' or ((x, y) in self.visited):  # ‘0’ node 和 已经染色过的‘1’ node,都不需要染色,不破坏原数组grid
                return False
            return True
    

      

    2. 并查集:

      初始化,针对1的node

      遍历所有节点,相邻节点合并1的node

      查询有多少不同的root(可以在上一步遍历的时候就顺便把结果维护了)

    class UnionFind:
        def __init__(self, grid):
            m, n = len(grid), len(grid[0])
            self.count = 0
            self.parent = [-1] * (m*n)  # -1 表示没有parent,水节点自身也不算子集。用一维数组存parent和rank
            self.rank = [0] * (m*n)  # 合并子集的优化
            
            for i in range(m):
                for j in range(n):
                    if grid[i][j] == '1':
                        self.parent[i*n + j] = i*n + j  # 初始时只有陆地节点联通自身
                        self.count += 1  # 计数初始化为所有陆地节点数
                        
        def find(self, i):
            """递归找到最顶的parent节点"""
            if self.parent[i] != i:
                self.parent[i] = self.find(self.parent[i])
            return self.parent[i]
        
        
        def union(self, x, y):
            rootx, rooty = self.find(x), self.find(y)
            if rootx != rooty:  # x, y 不属于一个子集,按rank合并两个子集
                if self.rank[rootx] > self.rank[rooty]:   # rank小的root挂到rank大的root上 
                    self.parent[rooty] = rootx
                elif self.rank[rootx] < self.rank[rooty]:   # rank小的root挂到rank大的root上 
                    self.parent[rootx] = rooty
                else:
                    self.parent[rooty] = rootx  # rank相等,随便挂,挂完要更新被挂root的rank
                    self.rank[rootx] += 1
                    
                self.count -= 1  # 合并完了就少一个算数的陆地节点,把count减1
                    
                        
    class Solution:
        def numIslands(self, grid: List[List[str]]) -> int:
            if not grid or not grid[0]:
                return 0
            
            uf = UnionFind(grid)
            directions = [(0, 1), (0, -1), (-1, 0), (1, 0)]
            m, n = len(grid), len(grid[0])
            
            for i in range(m):
                for j in range(n):
                    if grid[i][j] == '0':   # 如果是0节点就不管
                        continue
                    for d in directions:  # 向4方向扩散
                        new_i, new_j = i+d[0], j+d[1]
                        if 0 <= new_i < m and 0 <= new_j < n and grid[new_i][new_j] == '1':  # 如果不越界且为1节点,扩散合并附近节点
                            uf.union(i*n+j, new_i*n+new_j)
            return uf.count
    

      

    547. 朋友圈 https://leetcode-cn.com/problems/friend-circles/

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

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

    解:

    并查集,最多n个人自己和自己构成朋友圈,而且是无向图所以对称,只需要遍历右上角阵就行了。

    class UnionFind:
        def __init__(self, M):
            n = len(M)
            self.count = n  # 初始时候自己是一个朋友圈,count为n,parent[i]=i
            self.parent = [i for i in range(n)]  
            self.rank = [0] * n 
                         
        def find(self, i):
            """递归找到最顶的parent节点"""
            if self.parent[i] != i:
                self.parent[i] = self.find(self.parent[i])
            return self.parent[i]
        
        
        def union(self, x, y):
            rootx, rooty = self.find(x), self.find(y)
            if rootx != rooty:  # x, y 不属于一个子集,按rank合并两个子集
                if self.rank[rootx] > self.rank[rooty]:   # rank小的root挂到rank大的root上 
                    self.parent[rooty] = rootx
                elif self.rank[rootx] < self.rank[rooty]:   # rank小的root挂到rank大的root上 
                    self.parent[rootx] = rooty
                else:
                    self.parent[rooty] = rootx  # rank相等,随便挂,挂完要更新被挂root的rank
                    self.rank[rootx] += 1
                    
                self.count -= 1  # 合并完了就少一个算数的陆地节点,把count减1
                    
                    
    class Solution:
        def findCircleNum(self, M: List[List[int]]) -> int:
            if not M:
                return 0
            
            uf = UnionFind(M)
            n = len(M)
            
            for i in range(n):
                for j in range(i+1, n):
                    if M[i][j] == 0:   # 如果是0节点就不管,说明i, j没友谊
                        continue
                    uf.union(i, j)  # 否则合并i, j
            return uf.count
    

      

  • 相关阅读:
    ubuntu18 升级cmake
    开源镜像站汇总
    ubuntu18安装go
    tendermint框架及Tx执行流程
    常用python内置函数
    根据列号返回列名
    Valid Number
    Remove Duplicates from Sorted List II
    vector排序问题<unresolved overloaded function type>
    Spiral Matrix
  • 原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11386250.html
Copyright © 2011-2022 走看看