[路飞]_leetcode刷题_547. 省份数量

1,862 阅读2分钟

题目

547. 省份数量

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

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

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

返回矩阵中 省份 的数量。

示例 1:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

思路1:

图表的深度优先搜索

  1. isConnected的长度代表的是城市的总数
  2. 申明一个Set对象visited,用于存放遍历过的城市和于当前城市相连的城市,这类被记录下来的城市后序遍历会跳过。
  3. 申明一个计数器count,用于记录省份的数量
  4. 遍历isConnected中每一个城市,
    • 如果当前城市i没有遍历过,则递归深度查找,
    • 如果发现有城市j和i相连,并且这个城市j没有被visted记录过,则将城市j放入visted中,
    • 再递归的去查找城市j,是否有其他城市和它相连,直到没有为止,计数器count+1
  5. 最终返回这个count即可

代码如下:

/**
 * @param {number[][]} isConnected
 * @return {number}
 */
var findCircleNum = function(isConnected) {
    let cityNum = isConnected.length;
    let visited = new Set();
    let count = 0;
    for(let i=0;i<cityNum;i++){
        if(!visited.has(i)){
            dfs(isConnected,visited,cityNum,i);
            count++
        }
    }
    return count;
};

function dfs(isConnected,visited,cityNum,i){
    for(let j=0;j<cityNum;j++){
        if(isConnected[i][j] == 1 && !visited.has(j)){
            visited.add(j);
            dfs(isConnected,visited,cityNum,j)
        }
    }
}

复杂度分析:

时间复杂度:O(n^2),需要遍历每一个城市的同时,再去遍历所有城市,可以等同于循环套循环。

空间复杂度:O(n),这里vistedCity存放的城市的长度为n,另外递归调用栈的深度也不会超过n。

思路2:

图表的广度优先搜索

  1. isConnected的长度代表的是城市的总数
  2. 申明一个Set对象visited,用于存放遍历过的城市和于当前城市相连的城市,这类被记录下来的城市后序遍历会跳过。
  3. 申明一个计数器count,用于记录省份的数量
  4. 申明一个队列,用于存储待处理的城市
  5. 遍历isConnected
    • 如果当前城市i没有处理过,那么将它加入队列
    • 从队列取出头元素,并将当前城市放入visited,
    • 再去遍历isConnected,如果有城市和当前城市相连,则放入队列
    • 只要队列不为空,则一直处理下去,直到队列为空,则count+1,代表与城市i相连的所有城市都已经被放入了visited里了
  6. 最终返回count即位省份数

代码如下:

/**
 * @param {number[][]} isConnected
 * @return {number}
 */
var findCircleNum = function(isConnected) {
    let cityNum = isConnected.length;
    let visited = new Set();
    let count = 0;
    let queue = [];
    for(let i=0;i<cityNum;i++){
        if(!visited.has(i)){
           queue.push(i);
           while(queue.length>0){
               let j = queue.shift();
               visited.add(j);
               for(let k=0;k<isConnected.length;k++){
                   if(isConnected[j][k] == 1 && !visited.has(k)){
                       queue.push(k)
                   }
               }
           }
           count++;
        }
    }
    return count;
};

复杂度分析

时间复杂度:O(n^2),本质上每一个城市都去和所有城市比较一遍,就是n*n;

空间复杂度:O(n), 需要用visited记录所有的城市,即为n,另外队列的长度最多为n。

思路3:

并查集

我们可以利用并查集的数据结构,初始的连通量为isConnected的长度,如果两个城市相连,则将两个城市连接,最终计算连通量即可。

这里先要去了解并查集是什么,推荐看下这两篇文章,讲的很好。

# 详解并查集(基础篇) 作者:Name1e5s
# 最容易理解的并查集详解 作者:Chuancey

  1. 创建parent数组,用于记录城市之间的连接关系,初始化parent,元素都指向自己
  2. 创建连通量count,用于记录有多少个集合
  3. 创建连接方法union,将一个节点的根节点指向另一个节点的根节点
  4. 创建查找根节点方法find,这里用上路径压缩手段,最后扁平化处理,每个集合里,所有元素除了根节点,大家都在一层。
  5. 遍历isConnected数组,判断城市两两是否相邻,如果相邻,则将这两个节点在parent数组中连接
  6. 遍历parent,如果节点是跟节点,则count++
  7. 返回连通量count

代码如下:

/**
 * @param {number[][]} isConnected
 * @return {number}
 */
var findCircleNum = function(isConnected) {
    let count = 0;
    let len = isConnected.length;
    // 处理parent数组,初始化时,每个元素都指向自己
    let parent = new Array(len).fill(0).map((item,index)=>index)
    for(let i=0;i<len;i++){
        for(let j=i+1;j<len;j++){
            // 判断i和j,如果相连,则将其连接
            if(isConnected[i][j] == 1){
                union(parent,i,j);
            }
        }
    }
    for(let i=0;i<parent.length;i++){
        if(parent[i] == i){
            count++;
        }
    }
    return count;
};

function union(parent,p,q){
    // 连接p、q,将p的根节点指向q的根节点即可,反过来q的指向p也行
    let rootP = find(parent,p);
    let rootQ = find(parent,q);
    parent[rootP] = rootQ;
}

function find(parent,x){
    // 找x的根节点,并且压缩路径,将找到的根节点直接当作x的父节点
    if(parent[x] != x){
        parent[x] = find(parent,parent[x])
    }
    return parent[x];
}

复杂度分析

时间复杂度:O(n^2logn),遍历isConnected为n^2,遍历的同时要合并与查找,合并为O(1),查找我们这里使用了路径优化,在find查找父节点时时间复杂度为logn,所以总共为O(n^2logn)

推荐看一下大佬这篇并查集时间复杂度的分析

# 并查集各种情况下的时间复杂度 作者:zerotrac

空间复杂度:O(n),parent要存储所有节点的父节点情况。