[LeetCode] 1192. Critical Connections in a Network
There are n
servers numbered from 0
to n-1
connected by undirected server-to-server connections
forming a network where connections[i] = [a, b]
represents a connection between servers a
and b
. Any server can reach any other server directly or indirectly through the network.
A critical connection is a connection that, if removed, will make some server 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.
Constraints:
1 <= n <= 10^5
n-1 <= connections.length <= 10^5
connections[i][0] != connections[i][1]
- There are no repeated connections.
查找集群内的关键连接。
力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号。
它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群,其中连接 connections 是无向的。
从形式上讲,connections[i] = [a, b] 表示服务器 a 和 b 之间形成连接。任何服务器都可以直接或者间接地通过网络到达任何其他服务器。
「关键连接」是在该集群中的重要连接,也就是说,假如我们将它移除,便会导致某些服务器无法访问其他服务器。
请你以任意顺序返回该集群内的所有 「关键连接」。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/critical-connections-in-a-network
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题意是给一个正整数N表示一个集群内的node的个数,还有一个list of list表示每两个node时间的连接关系,请返回集群内的关键连接。比如上面这个例子,关键连接是[1, 3]。这个题的本质是在求无向图中的桥,我直接给出最优解,Trojan算法。这个算法也就是专门为着找桥而存在的。算法的具体解释,我参考了这个帖子,写的很好。至于具体的算法,我们需要
- 建立这个图 - 这个没什么可说的
- 一个全局变量time,记录traverse的时间戳
- 一个parent数组,记录每个node的parent节点
- 一个discovery数组,记录每个node被访问到的时间
- 一个low数组,默认是记录一个跟discovery数组一样的时间,但是如果当前节点的下一个邻居的low time < 当前节点的discovery,则把当前节点的low time更新,意思是在环中的任何一个节点能被访问到的时间等同于环中能被访问到的节点所需的最小用时,有点类似于union find的分组
如果一个节点(比如U好了)没有被访问过,那么我们从这个U开始做DFS。对于DFS本身,如果已经访问过的节点则直接return,这样避免出现环;接着我们开始访问U的所有邻居节点。对于每一个邻居节点V来说,如果没有被访问过,则记录V的parent节点是U,同时接着往下做DFS递归。其余请参见代码注释。
Java实现
时间O(V + E)
空间O(n)
1 class Solution { 2 // global variable 3 int time = 0; 4 5 public List<List<Integer>> criticalConnections(int n, List<List<Integer>> connections) { 6 List<List<Integer>> res = new ArrayList<>(); 7 List<Integer>[] graph = new List[n]; 8 buildGraph(graph, connections); 9 10 // discover到某个节点所需时间,对于同一棵子树上的节点,理论上遍历到父亲节点的时间应该比其子节点的时间要少 11 int[] discovery = new int[n]; 12 // lowest vertex in subtree, 某个节点最早被访问到的时间 13 // 对于一条边U->V来说,如果到达V的时间无法比到到U的时间更短,也就是说V的discoveryTime无法缩短,那么U-V就是桥 14 int[] low = new int[n]; 15 // 每个节点的parent节点 16 int[] parents = new int[n]; 17 Arrays.fill(discovery, -1); 18 Arrays.fill(low, -1); 19 Arrays.fill(parents, -1); 20 21 for (int i = 0; i < n; i++) { 22 // 如果没有被访问过,就开始做DFS 23 if (discovery[i] == -1) { 24 dfs(i, parents, low, discovery, res, graph); 25 } 26 } 27 return res; 28 } 29 30 private void dfs(int u, int[] parents, int[] low, int[] discovery, List<List<Integer>> res, List<Integer>[] graph) { 31 // 已经访问过 32 if (discovery[u] != -1) { 33 return; 34 } 35 low[u] = discovery[u] = time++; 36 for (int v : graph[u]) { 37 // 对于u的每一个邻居v而言,如果v没有被访问过,那么把u标记成v的parent节点并接着做DFS 38 if (discovery[v] == -1) { 39 parents[v] = u; 40 dfs(v, parents, low, discovery, res, graph); 41 if (low[v] > discovery[u]) { 42 res.add(Arrays.asList(u, v)); 43 } 44 low[u] = Math.min(low[u], low[v]); 45 } 46 // 如果v被访问过,但是u不是v的父节点,那么有可能traverse到u的代价会更小,就需要更新 47 else if (parents[u] != v) { 48 low[u] = Math.min(low[u], discovery[v]); 49 } 50 } 51 } 52 53 // 图的创建 54 private void buildGraph(List<Integer>[] graph, List<List<Integer>> connections) { 55 for (List<Integer> c : connections) { 56 int a = c.get(0); 57 int b = c.get(1); 58 if (graph[a] == null) { 59 graph[a] = new ArrayList<>(); 60 } 61 if (graph[b] == null) { 62 graph[b] = new ArrayList<>(); 63 } 64 graph[a].add(b); 65 graph[b].add(a); 66 } 67 } 68 }