【并查集】B000_朋友圈(并查集 | DFS | BFS)

一、题目描述

Given a N*N matrix M representing the friend relationship between students in the class. 
If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. 
And you have to output the total number of friend circles among all the students.

Example 1:
Input: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. 
The 2nd student himself is in a friend circle. So return 2.

Example 2:
Input: 
[[1,1,0],
 [1,1,1],
 [0,1,1]]
Output: 1
Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends, 
so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1.

Note:
N is in range [1,200].
M[i][i] = 1 for all students.
If M[i][j] = 1, then M[j][i] = 1.

二、题解

(1) 并查集

这道题的本质是寻找连通块的数量,使用一个大小为 N N p a r e n t parent 数组,对于每一个为 1 1 的结点我们都遍历它的相邻结点,并让相邻结点指向该结点(即,让他们的父亲结点为)。

如果他们的父亲结点一样,那么他们在同一个组里,如果不一样,那么需要合并到同一个组里。简单模拟一下,假如二维数组 M M 为:

[[1,1,0],
 [1,1,1],
 [0,1,1]]
  • 初始状态下,连通分量 c o u n t count 的数量 == M . l e n g t h M.length ,即 3 3 p a r e n t [ 0 ] = 0 , [ 1 ] = 1 , [ 2 ] = 2 parent[0]=0, [1]=1, [2]=2
  • 如果找到一个 1 1 ,则看它是否属于一个连通分量比如 ( 0 , 0 ) (0, 0) 就是一个连通分量下的,则不需要 u n i o n union ,直接 r e t u r n return
  • i++ 后, M [ 1 ] [ 0 ] = = 1 M[1][0]==1 ,继续上述过程,但此时 ( 1 , 0 ) (1,0) 并不是一个连通分量下的,所以他们被合并了,连通分量变为 c o u n t 1 = = 2 count-1 == 2
  • 第二个 f o r for 循环的边界为 j < i j<i 的原因是:比如 ( 2 , 2 ) ( 3 , 3 ) (2,2),(3,3) 点这些表示自己跟自己组成一个朋友圈,计算无意义。
  • 二维矩阵 M M 是正方形,则可推出对角线上的元素都是 i = = j i==j ,无需计算。

如果不理解,可以将方法中的 f o r for 循环变为以下代码,只不过这里有重复计算,比如坐标 ( 0 , 1 ) (0,1) ( 1 , 0 ) (1, 0)

for(int i = 0; i < len; i++) 
for(int j = 0; j < len; j++) 
if(i != j && M[i][j] == 1) 
    uf.union(i, j);
i , j i, j M [ i ] [ j ] M[i][j] u n i o n ( i , j ) union(i, j) f i n d ( i ) f i n d ( j ) find(i),find(j) p R o o t I D q R o o t I D pRootID,qRootID
0,0 1 union(0, 0) find(0),find(0) 0,0
1,0 1 union(1, 0) find(1),find(0) 1,0
2,0 0
2,1 0
public class Solution {
  class UnionFind {
    private int[] parent;  // 存储结点的父节点 id
    private int count;     // 结点数量
    private int[] rank;    // rank[i]表示一i为根的集合(树)的最大高度
    UnionFind(int N) {
      count = N;
      parent = new int[N];
      rank = new int[N];
      for(int i = 0; i < N; i++) {
        parent[i] = i;
        rank[i] = 1;
      }
    }
    public int count()  { return count; }

    public int find(int p) {
      if(p < 0 || p >= parent.length)
        throw new IllegalArgumentException("p is out of bound");

      while(p != parent[p]) {
        parent[p] = parent[parent[p]];
        p = parent[p];
      }
      return p;
    }

    public boolean isConnected(int p, int q) {
      return find(p) == find(q);
    }

    public void union(int p, int q) {
      int pRootID = find(p);
      int qRootID = find(q);

      if(pRootID == qRootID)  return;

      if(rank[pRootID] > rank[qRootID])
        parent[qRootID] = pRootID;
      else if(rank[pRootID] < rank[qRootID])
        parent[pRootID] = qRootID;
      else {
        parent[pRootID] = qRootID;
        rank[qRootID]++;
      }
      count--; 
    }
  }
  public int findCircleNum(int[][] M) {
    int len = M.length;
    UnionFind uf = new UnionFind(len);
    for(int i = 0; i < len; i++) 
    for(int j = 0; j < i; j++) 
    if(M[i][j] == 1) 
      uf.union(i, j);

    return uf.count();
  }
}

复杂度分析

  • 时间复杂度: O ( N 2 ) O(N^2) ,加入路径压缩, f i n d find 方法的时间复杂度从 O ( n ) / O ( l o g n ) O(n)/O(logn) 变为 O ( 1 ) O(1) ,访问半个矩阵需要 O ( n 2 ) O(n^2)
  • 空间复杂度: O ( 2 N ) O(2*N) p a r e n t r a n k parent、rank 数组大小都为 N N N N 为二维数组 M M 的宽。

(2) dfs

这一题与以往的图遍历题目稍微有不同,如果 ( x 1 , y 1 ) (x1,y1) 1 1 ,那么 ( y 1 , x 1 ) (y1,x1) 也必定是 1 1 ,这样的话,我们只需要记录每一行的是否访问过的状态就行了。

int rows;
int cols;
int count;
boolean[] isVisited;
public int findCircleNum(int[][] M) {
  rows = M.length;
  cols = M.length;
  isVisited = new boolean[cols];
  for (int i = 0; i < rows; i++)
  if(!isVisited[i]) {
    dfs(M, i);
    count++;
  }
  return count;
}

/**
 * @date: 1/20/2020 8:29 PM
 * @Execution info:1ms 击败 100% 的j,44MB 击败 36% 的j
 */
public void dfs(int[][] M, int i) {
  isVisited[i] = true;
  for (int j = 0; j < M[0].length; j++)
  if(!isVisited[j] && M[i][j] == 1) {
    isVisited[j] = true;
    dfs(M, j);
  }
}
  • 时间复杂度: O ( n 2 ) O(n^2) ,整个矩阵都要被遍历,大小为 n 2 n ^2
  • 空间复杂度: O ( n ) O(n) i s V i s i t e d isVisited 数组的大小。

(3) bfs

b f s bfs 的代码用队列存储符合条件的结点。

/**
 * @date: 1/20/2020 8:29 PM
 * @Execution info:10ms 击败 22% 的j,MB 击败 5.13% 的j
 */
public void bfs(int[][] M, int i, boolean[] isVisited) {
  Queue<Integer> queue = new LinkedList<>();
  queue.add(i);
  while (!queue.isEmpty()) {
    int t = queue.poll();
    isVisited[t] = true;
    for (int j = 0; j < M[0].length; j++)
    if(!isVisited[j] && M[t][j] == 1)
      queue.add(j);
  }
}

同上 d f s dfs

发布了300 篇原创文章 · 获赞 48 · 访问量 8044

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/104056289