Leetcode: 省份数量

16 阅读3分钟

原题:leetcode.cn/problems/nu…

题目描述

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

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

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

返回矩阵中 省份 的数量。

解题思路

  1. 图论建模
    • 将城市视为节点,连接关系视为边
    • 问题转化为求无向图的连通分量数量
  2. 深度优先搜索 (DFS)
    • 从每个未访问城市出发递归标记连通城市
    • 统计DFS启动次数即为省份数量
  3. 广度优先搜索 (BFS)
    • 使用队列迭代访问连通城市
    • 同样统计BFS启动次数
  4. 并查集 (Union-Find)
    • 初始化每个城市为独立集合
    • 合并相连城市,最后统计根节点数量
  5. 邻接矩阵利用
    • 直接使用输入矩阵避免额外存储

关键洞察

  1. 连通分量本质
    • 省份等价于图的连通分量
  2. 矩阵对称性
    • 邻接矩阵对称,可优化遍历范围
  3. 访问标记
    • 避免重复访问同一城市
  4. 对角线处理
    • 对角线恒为1(城市自身连接)
  5. 高效合并
    • 并查集路径压缩优化性能
  6. 空间优化
    • 直接修改访问标记减少空间占用

复杂度分析

指标DFS/BFS并查集
时间复杂度O(n²) : 遍历整个矩阵O(n² α(n)) : 近似线性
空间复杂度O(n) : 访问标记数组O(n) : 父节点存储
最优情况O(n) : 全连通图O(n α(n)) : 合并次数少
最坏情况O(n²) : 稀疏连接O(n² α(n)) : 完全遍历

代码实现

  • JavaScript(DFS)
    const findCircleNum = (isConnected) => {
    const n = isConnected.length;
    const visited = new Array(n).fill(false);
    let provinces = 0;
    
    const dfs = (i) => {
        visited[i] = true;
        for (let j = 0; j < n; j++) {
            if (isConnected[i][j] === 1 && !visited[j]) {
                dfs(j);
            }
        }
    };
    
    for (let i = 0; i < n; i++) {
        if (!visited[i]) {
            provinces++;
            dfs(i);
        }
    }
    
    return provinces;
    };
    
  • python(BFS)
    from collections import deque
    
    class Solution:
        def findCircleNum(self, isConnected: List[List[int]]) -> int:
            n = len(isConnected)
    
            visited = [False] * n
            provinces = 0
    
            for i in range(n):
                if not visited[i]:
                    provinces += 1
                    queue = deque([i])
                    visited[i] = True
    
                    while queue:
                        city = queue.popleft()
                        for neighbor in range(n):
                            if isConnected[city][neighbor] == 1 and not visited[neighbor]:
                                visited[neighbor] = True
                                queue.append(neighbor)
            return provinces
    
  • rust(并查集)
    struct UnionFind {
        parent: Vec<usize>,
        count: usize,
    }
    
    impl UnionFind {
        fn new(n: usize) -> Self {
            let parent = (0..n).collect();
            UnionFind { parent, count: n }
        }
        
        fn find(&mut self, x: usize) -> usize {
            if self.parent[x] != x {
                self.parent[x] = self.find(self.parent[x]);
            }
            self.parent[x]
        }
        
        fn union(&mut self, x: usize, y: usize) {
            let root_x = self.find(x);
            let root_y = self.find(y);
            if root_x != root_y {
                self.parent[root_y] = root_x;
                self.count -= 1;
            }
        }
    }
    
    impl Solution {
        pub fn find_circle_num(is_connected: Vec<Vec<i32>>) -> i32 {
    	    let n = is_connected.len();
    	    let mut uf = UnionFind::new(n);
    	    for i in 0..n {
    	        for j in (i + 1)..n {
    	            if is_connected[i][j] == 1 {
    	                uf.union(i, j);
    	            }
    	        }
    	    }
    	    uf.count as i32
        }
    }
    

实际应用场景

  1. 社交网络:计算用户社交圈数量`
  2. 网络运维:检测服务器集群连通性`
  3. 城市规划:计算城市群数量`
  4. 生物信息学:蛋白质相互作用网络分析`

相似题目