在问 · 算法题解

1319. 连通网络的操作次数

中等图论并查集统计连通分量力扣原题 ↗

题目描述

有 n 台计算机,编号从 0 到 n - 1。输入数组 connections,connections[i] = [a, b] 表示有一根网线直接连着 a 和 b。通过网线直接或间接相连的计算机可以互相访问。

每次操作可以拔下一根已有的网线,改接到两台原本没有直接相连的计算机之间。

返回让所有计算机都能互相访问所需的最少操作次数。如果网线总数不够,无论怎么接都做不到,返回 -1。

示例 1
输入:n = 4, connections = [[0,1],[0,2],[1,2]]
输出:1

解释:把 1 和 2 之间的网线改接到 1 和 3 之间。

示例 2
输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]]
输出:2

解释:4 和 5 各自孤立,有两根多余的网线可以改接,正好需要 2 次。

示例 3
输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2]]
输出:-1

解释:6 台计算机至少需要 5 根网线,这里只有 4 根。

数据范围

  • 1 <= n <= 10^5
  • 1 <= connections.length <= min(n*(n-1)/2, 10^5)
  • connections[i].length == 2
  • 0 <= connections[i][0], connections[i][1] < n
  • connections[i][0] != connections[i][1]
  • 没有重复的连接。
  • 任意两台计算机之间最多只有一根网线。

解题思路:并查集统计连通分量

连通 n 台机器至少需要 n-1 根线,不够直接返回 -1。线够的话,答案就是“连通块数 − 1”:每次拿一根多余的线(在同一块内成环的线)去连两个块。用并查集遍历所有线,每合并一次块数减 1。

为什么正确

处理完前 t 根线后,parts 等于只用这 t 根线时的连通块数。

复杂度分析

带路径压缩的并查集,时间近似 O(n + E),空间 O(n)。

易错点

不要去数“多余的线”和块数比较;先判断 E ≥ n-1,就保证多余的线一定够用。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def makeConnected(self, n: int, connections: List[List[int]]) -> int:
        if len(connections) < n - 1:
            return -1
        parent = list(range(n))

        def find(x):
            while parent[x] != x:
                parent[x] = parent[parent[x]]
                x = parent[x]
            return x

        parts = n
        for a, b in connections:
            ra, rb = find(a), find(b)
            if ra != rb:
                parent[ra] = rb
                parts -= 1
        return parts - 1

载入编辑器,自己跑一遍 →

其他解法

两种方法都在统计连通分量个数。并查集逐条处理边,适合边动态加入的场景;DFS 需要先建好整张图再遍历,思路最直观。

解法 2:深度优先搜索统计连通分量 时间 O(n + m),空间 O(n + m)

先判断线缆数是否至少为 n − 1,不够就一定连不通。线缆够用时,每条多余的线都能拆下来连接两个分量,所以答案只取决于连通分量的个数 k,等于 k − 1。用邻接表建图,从每个还没访问过的电脑出发做一次深度优先搜索,把能到达的电脑全部标记,搜索的次数就是连通分量个数。

图的遍历最直观,不需要理解并查集的路径压缩和合并。需要先建邻接表,占用 O(n + m) 额外空间;并查集只要一个父数组,而且能边读边合并。

class Solution:
    def makeConnected(self, n: int, connections: List[List[int]]) -> int:
        if len(connections) < n - 1:
            return -1
        adj = [[] for _ in range(n)]
        for a, b in connections:
            adj[a].append(b)
            adj[b].append(a)
        seen = [False] * n
        parts = 0
        for start in range(n):
            if seen[start]:
                continue
            parts += 1
            seen[start] = True
            stack = [start]
            while stack:
                u = stack.pop()
                for v in adj[u]:
                    if not seen[v]:
                        seen[v] = True
                        stack.append(v)
        return parts - 1

图论 专题的其他题目

查看 图论 全部题目 →