There are n
servers numbered from 0
to n - 1
connected by undirected server-to-server connections
forming a network where connections[i] = [ai, bi]
represents a connection between servers ai
and bi
. Any server can reach other servers directly or indirectly through the network.
A critical connection is a connection that, if removed, will make some servers unable to reach some other server.
Return all critical connections in the network in any order.
Example 1:
Input: n = 4, connections = [[0,1],[1,2],[2,0],[1,3]]
Output: [[1,3]]
Explanation: [[3,1]] is also accepted.
Example 2:
Input: n = 2, connections = [[0,1]]
Output: [[0,1]]
Constraints:
2 <= n <= 10^5
n - 1 <= connections.length <= 10^5
0 <= ai, bi <= n - 1
ai != bi
- There are no repeated connections.
這道題說是有n個服務器互相連接,定義了一種關鍵連接,就是當去掉后,會有一部分服務無法訪問另一些服務。說白了,就是讓求無向圖中的橋,對於求無向圖中的割點或者橋的問題,需要使用塔里安算法 Tarjan's Algorithm。該算法是圖論中非常常用的算法之一,能解決強連通分量,雙連通分量,割點和橋,求最近公共祖先(LCA)等問題,可以參見知乎上的這個貼子。Tarjan 算法是一種深度優先遍歷 Depth-first Search 的算法,而且還帶有一點聯合查找 Union Find 的影子在里面。和一般的 DFS 遍歷不同的是,這里用了一個類似時間戳的概念,就是用一個 time 數組來記錄遍歷到當前結點的時間,初始化為1,每遍歷到一個新的結點,時間值就增加1。這里還用了另一個數組 low,來記錄在能夠訪問到的所有節點中,時間戳最小的值,這樣,在一個環上的結點最終 low 值都會被更新成一個最小值,就好像 Union Find 中同一個群組中都有相同的 root 值一樣。這是非常重要且聰明的處理方式,因為若當前結點是割點的話,即其實橋一頭的端點,當過橋之后,由於不存在其他的路徑相連通(假設整個圖只有一個橋),那么無法再回溯回來的,所以不管橋另一頭的端點 next 的 low 值如何更新,其一定還是會大於 cur 的 low 值的,則橋就找到了。可能干講不好理解,建議看下上面提到的知乎帖子,里面有圖和視頻可以很好的幫助理解。
這里首先根據給定的邊來建立圖的結構,這里使用 HashMap 來建立結點和其所有相連的結點集合之間的映射。對於每條邊,由於是無向圖,則兩個方向都要建立映射,然后就調用遞歸,要傳的參數還真不少,圖結構,當前結點,前一個結點,cnt 值,time 和 low 數組,還有結果 res。在遞歸函數中,首先給當前結點 cur 的 time 和 low 值都賦值為 cnt,然后 cnt 自增1。接下來遍歷和當前結點所有相鄰的結點,若 next 的時間戳為0,表示這個結點沒有被遍歷過,則對該結點調用遞歸,然后用 next 結點的 low 值來更新當前結點的 low 值。若 next 結點已經之前訪問過了,但不是前一個結點,則用 next 的 time 值來更新當前結點的 low 值。若回溯回來之后,next 的 low 值仍然大於 cur 的 time 值,說明這兩個結點之間沒有其他的通路,則一定是個橋,加入到結果 res 中即可,參見代碼如下:
class Solution {
public:
vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
int cnt = 1;
vector<vector<int>> res;
vector<int> time(n), low(n);
unordered_map<int, vector<int>> g;
for (auto conn : connections) {
g[conn[0]].push_back(conn[1]);
g[conn[1]].push_back(conn[0]);
}
helper(g, 0, -1, cnt, time, low, res);
return res;
}
void helper(unordered_map<int, vector<int>>& g, int cur, int pre, int& cnt, vector<int>& time, vector<int>& low, vector<vector<int>>& res) {
time[cur] = low[cur] = cnt++;
for (int next : g[cur]) {
if (time[next] == 0) {
helper(g, next, cur, cnt, time, low, res);
low[cur] = min(low[cur], low[next]);
} else if (next != pre) {
low[cur] = min(low[cur], time[next]);
}
if (low[next] > time[cur]) {
res.push_back({cur, next});
}
}
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1192
參考資料:
https://leetcode.com/problems/critical-connections-in-a-network/
https://zhuanlan.zhihu.com/p/101923309