算法-並查集/DFS/BFS-朋友圈
1 題目概述
1.1 題目出處
https://leetcode-cn.com/problems/friend-circles/
1.2 題目描述
班上有 N 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那麼我們可以認爲 A 也是 C 的朋友。所謂的朋友圈,是指所有朋友的集合。
給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。如果M[i][j] = 1,表示已知第 i 個和 j 個學生互爲朋友關係,否則爲不知道。你必須輸出所有學生中的已知的朋友圈總數。
示例 1:
輸入:
[[1,1,0],
[1,1,0],
[0,0,1]]
輸出: 2
說明:已知學生0和學生1互爲朋友,他們在一個朋友圈。
第2個學生自己在一個朋友圈。所以返回2。
示例 2:
輸入:
[[1,1,0],
[1,1,1],
[0,1,1]]
輸出: 1
說明:已知學生0和學生1互爲朋友,學生1和學生2互爲朋友,所以學生0和學生2也是朋友,所以他們三個在一個朋友圈,返回1。
注意:
- N 在[1,200]的範圍內。
- 對於所有學生,有M[i][i] = 1。
- 如果有M[i][j] = 1,則有M[j][i] = 1。
2 並查集
2.1 思路
遍歷每個學生,並從該學生的下一個序號開始處理,只要值爲1代表兩個學生爲朋友,此時就進行並查集合並代表同屬一個朋友圈。
最後返回並查集內不同root祖先個數,即得到朋友圈個數。
2.2 代碼
class Solution {
public int findCircleNum(int[][] M) {
if(M == null || M.length == 0){
return 0;
}
// 並查集,大小爲學生個數
int[] unionFindSet = new int[M.length];
// 初始化並查集值爲元素序號本身
for(int i = 1; i < unionFindSet.length; i++){
unionFindSet[i] = i;
}
// 按行遍歷每個學生
for(int i = 0; i < unionFindSet.length; i++){
// 從每個學生的下一個序號開始遍歷處理朋友關係
for(int j = i + 1; j < unionFindSet.length; j++){
// 只要值爲1,代表i學生和j號學生是朋友,則將他們的並查集合並
if(M[i][j] == 1){
join(unionFindSet, i, j);
}
}
}
// 用來統計朋友圈的HashSet
Set<Integer> filterSet = new HashSet<>();
// 遍歷並查集,看不同的root祖先有幾個就知道有幾個不相連的並查集
// 即有幾個朋友圈
for(int id : unionFindSet){
filterSet.add(search(unionFindSet, id));
}
return filterSet.size();
}
/**
* 將兩個元素的祖先節點所在並查集合並
*/
private void join(int[] unionFindSet, int p, int q){
//
int left = search(unionFindSet,p);
int right = search(unionFindSet,q);
if (left == right){
// 同一並查集
return;
}
// 將p元素的root祖先節點的祖先設爲q元素的root祖先節點
// 這樣就合併爲了一個並查集
unionFindSet[left] = right;
}
/**
* 尋找並查集root祖先
*/
private int search(int[] unionFindSet, int p){
int son = p;
// 從當前節點開始迭代查找祖先
// 直到某個元素祖先節點是自己,那就是找到了root祖先元素
while(p != unionFindSet[p]){
p = unionFindSet[p];
}
// 路徑壓縮算法,祖先的祖先全部設爲p,高度變爲2
while(son != p){
int tmp = unionFindSet[son];
unionFindSet[son] = p;
son = tmp;
}
// 返回root祖先元素
return p;
}
}
2.3 時間複雜度
2.4 空間複雜度
O(N)
3 圖-DFS
3.1 思路
按行遍歷學生,只要沒訪問過就說明屬於一個新的獨立朋友圈,count加1。此時,開始從下標爲i+1的開始遍歷其他學生j,只要M[i,j] == 1 說明是朋友,開始對學生j dfs。一趟dfs中遍歷到的學生都屬於這次i學生的朋友圈,只計數1次。
最後返回count即可。
3.2 代碼
class Solution {
public int findCircleNum(int[][] M) {
if(M == null || M.length == 0){
return 0;
}
// 朋友圈個數
int count = 0;
// 記錄是否已遍歷
int[] visited = new int[M.length];
// 按行遍歷每個學生
for(int i = 0; i < M.length; i++){
if(visited[i] == 1){
continue;
}
// 沒人訪問過該學生,說明該學生目前是個獨立朋友圈
// 那麼此時遍歷到的關係爲1的學生以及由此進行dfs訪問到的學生同屬i學生的一個朋友圈
count++;
// 標記該學生已訪問過
visited[i] = 1;
// 從每個學生的下一個序號開始遍歷處理朋友關係
for(int j = i + 1; j < M.length; j++){
// 只要值爲1,代表i學生和j號學生是朋友,則將他們的朋友圈合併
if(visited[j] == 0 && M[i][j] == 1){
dfs(M, j, visited);
}
}
}
return count;
}
/**
* i作爲朋友圈代號
* j爲當前遍歷學生序號
*/
private void dfs(int[][] M, int j, int[] visited){
visited[j] = 1;
// 這裏注意要出0開始,因爲有可能還沒訪問過的、序號更小的節點和當前節點是朋友
// 原因是採用了DFS,無順序性!!
for(int k = 0; k < M.length; k++){
// 只要值爲1,代表j學生和k號學生是朋友,則將他們的朋友圈合併
if(visited[k] == 0 && M[j][k] == 1){
dfs(M, k, visited);
}
}
}
}
3.3 時間複雜度
3.4 空間複雜度
O(N)
- 因爲需要遞歸
4 BFS
4.1 思路
4.2 代碼