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
    

      

  • 相关阅读:
    创建类以及引用一个类
    修改hosts文件
    微信第三方登录接口开发
    Android定位
    Leetcode 102. Binary Tree Level Order Traversal
    Leetcode 725. Split Linked List in Parts
    Leetcode 445. Add Two Numbers II
    Leetcode 328. Odd Even Linked List
    Leetcode 237. Delete Node in a Linked List
    Leetcode 234. Palindrome Linked List
  • 原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11386250.html
Copyright © 2011-2022 走看看