Day 78 并查集:323. 无向图中连通分量的数目, 130. Surrounded Regions, 990. Satisfiability of Equality Equations

如果节点 p 和 q 连通的话,一定拥有相同的根节点


class UF {
    // 记录连通分量
    private int count;
    // 节点 x 的父节点是 parent[x]
    private int[] parent;

    /* 构造函数,n 为图的节点总数 */
    public UF(int n) {
        // 一开始互不连通
        this.count = n;
        // 父节点指针初始指向自己
        parent = new int[n];
        for (int i = 0; i < n; i++)
            parent[i] = i;
    }

    /* 其他函数 */
}

public void union(int p, int q) {
    int rootP = find(p);
    int rootQ = find(q);
    if (rootP == rootQ)
        return;
    // 将两棵树合并为一棵
    parent[rootP] = rootQ;
    // parent[rootQ] = rootP 也一样
    count--; // 两个分量合二为一
}

/* 返回某个节点 x 的根节点 */
private int find(int x) {
    // 根节点的 parent[x] == x
    while (parent[x] != x)
        x = parent[x];
    return x;
}

/* 返回当前的连通分量个数 */
public int count() { 
    return count;
}

public boolean connected(int p, int q) {
    int rootP = find(p);
    int rootQ = find(q);
    return rootP == rootQ;
}

复杂度:O(n)


平衡性优化, 复杂度:O(log n)


class UF {
    private int count;
    private int[] parent;
    // 新增一个数组记录树的“重量”
    private int[] size;

    public UF(int n) {
        this.count = n;
        parent = new int[n];
        // 最初每棵树只有一个节点
        // 重量应该初始化 1
        size = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }
    /* 其他函数 */
}

public void union(int p, int q) {
    int rootP = find(p);
    int rootQ = find(q);
    if (rootP == rootQ)
        return;
    
    // 小树接到大树下面,较平衡
    if (size[rootP] > size[rootQ]) {
        parent[rootQ] = rootP;
        size[rootP] += size[rootQ];
    } else {
        parent[rootP] = rootQ;
        size[rootQ] += size[rootP];
    }
    count--;
}

路径压缩
进一步压缩每棵树的高度,使树高始终保持为常数


// 第一种路径压缩的 find 方法
private int find(int x) {
    while (parent[x] != x) {
        // 这行代码进行路径压缩
        parent[x] = parent[parent[x]];
        x = parent[x];
    }
    return x;
}
// 第二种路径压缩的 find 方法
public int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}
class UF {
    // 连通分量个数
    private int count;
    // 存储每个节点的父节点
    private int[] parent;

    // n 为图中节点的个数
    public UF(int n) {
        this.count = n;
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    
    // 将节点 p 和节点 q 连通
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        
        if (rootP == rootQ)
            return;
        
        parent[rootQ] = rootP;
        // 两个连通分量合并成一个连通分量
        count--;
    }

    // 判断节点 p 和节点 q 是否连通
    public boolean connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    // 返回图中的连通分量个数
    public int count() {
        return count;
    }
}

Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要 O(N) 的时间和空间复杂度;连通两个节点 union、判断两个节点的连通性 connected、计算连通分量 count 所需的时间复杂度均为 O(1)。

323. Number of Connected Components in an Undirected Graph

  • 思路
    • example
    • DFS
  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        def dfs(graph, i):
            visited[i] = True 
            for j in graph[i]:
                if not visited[j]:
                    dfs(graph, j)
        # 建立无向图(邻接表)  
        graph = collections.defaultdict(list)
        for edge in edges:
            graph[edge[0]].append(edge[1])
            graph[edge[1]].append(edge[0])
        cnt = 0
        visited = [False for _ in range(n)] 
        for i in range(n):
            if not visited[i]:
                dfs(graph, i)
                cnt += 1
        return cnt 
  • 并查集
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        class UF:
            def __init__(self, n):
                self.count = n 
                self.parent = [i for i in range(n)] 
            def find(self, p):
                if p != self.parent[p]:
                    self.parent[p] = self.find(self.parent[p])
                return self.parent[p]
            def union(self, p, q):
                if self.find(p) == self.find(q):
                    return 
                # self.parent[self.parent[p]] = self.parent[q] # wrong
                self.parent[self.find(p)] = self.find(q)
                self.count -= 1
        uf = UF(n)
        for edge in edges:
            uf.union(edge[0], edge[1])
        return uf.count 

130. Surrounded Regions

  • 思路
    • example
    • DFS (更自然)

先用 for 循环遍历棋盘的四边,用 DFS 算法把那些与边界相连的 O 换成一个特殊字符,比如 #;然后再遍历整个棋盘,把剩下的 O 换成 X,把 # 恢复成 O。

  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def dfs(board, i, j, replacement):
            if i < 0 or i >= m or j < 0 or j >= n:
                return 
            if board[i][j] != 'O':
                return 
            board[i][j] = replacement
            directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
            for direction in directions:
                dx, dy = direction[0], direction[1]
                x, y = i + dx, j + dy   
                dfs(board, x, y, replacement) 
        m, n = len(board), len(board[0])
        # 边界
        for j in range(n):
            if board[0][j] == 'O':
                dfs(board, 0, j, '#')
            if board[m-1][j] == 'O':
                dfs(board, m-1, j, '#')
        for i in range(m):
            if board[i][0] == 'O':
                dfs(board, i, 0, '#')
            if board[i][n-1] == 'O':
                dfs(board, i, n-1, '#')
        # 内部以及边界
        for i in range(m):
            for j in range(n):
                if board[i][j] == 'O':
                    board[i][j] = 'X'
                if board[i][j] == '#':
                    board[i][j] = 'O'  
  • 并查集


code

990. Satisfiability of Equality Equations

  • 思路
    • example
    • 类似于检查冲突

动态连通性其实就是一种等价关系
核心思想是,将 equations 中的算式根据 == 和 != 分成两部分,先处理 == 算式,使得他们通过相等关系各自勾结成门派(连通分量);然后处理 != 算式,检查不等关系是否破坏了相等关系的连通性。

  • 复杂度. 时间:O(?), 空间: O(?)
class Solution:
    def equationsPossible(self, equations: List[str]) -> bool:
        class UF:
            def __init__(self, n):
                self.count = n 
                self.parent = [i for i in range(n)]
            def find(self, p):
                if p != self.parent[p]:
                    self.parent[p] = self.find(self.parent[p])
                return self.parent[p]
            def union(self, p, q):
                rootP = self.find(p)
                rootQ = self.find(q)
                if rootP == rootQ:
                    return 
                self.parent[rootP] = rootQ  
                self.count -= 1
            def connected(self, p, q):
                return self.find(p) == self.find(q)
        uf = UF(26) # 26 letters
        # 连通分类
        for equation in equations:
            if equation[1] == '=':
                p, q = equation[0], equation[3]
                uf.union(ord(p) - ord('a'), ord(q) - ord('a'))
        # 检查连通性
        for equation in equations:
            if equation[1] == '!':
                p, q = equation[0], equation[3]
                if uf.connected(ord(p)-ord('a'), ord(q)-ord('a')):
                    return False 
        return True 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容