1192. Critical Connections in a Network
解题思路
本题是个 hard。
题目要求我们找出关键链接,所谓关键链接就是如果去掉的话,会导致本来连通的节点不连通了
观察题目给出的图示,我们不难看出,关键链接就是不在环上的链接(因为只要有多个节点成环了,那么环内的每个节点至少会有两条边)
有了上述结论,我们可以把问题抽象为:找出图中所有的环,并忽略环上的边,剩下的边就是关键链接
比如题目中的 Example 1 可以看成:
这样一来,问题就变成了两个:
Q:如何标记一个环?
A:我们给每一个每一个节点一个独一无二 id,如果成环了,则环内节点 id 全部设置为当前环内节点 id 的最小值
Q:如何在图中找出环?
A:使用 dfs,只要遍历过程中遇到了遍历过的节点,就表示遇到环了,dfs 返回当前节点的 id
接下来,我们来理一下算法的思路:
- 首先我们需要建立一个表 map,保存所有节点的邻接节点
- 然后我们需要一个数组 groupId,保存每个节点的 id,初始化为 -1
- 任选一个节点为根节点,对其进行 dfs,将根节点的 id 置为 0
在 dfs 中,然后遍历当前节点的邻接节点,此时会有三种可能性:
- 邻接节点为当前节点的父节点:直接 continue
- 邻接节点还没遍历过
id == -1
:对邻接节点进行 dfs,设置其 id 为当前 id+1,如果 dfs 返回的 id 比当前 id 小,更新当前 id - 邻接节点遍历过了
id != -1
:表示遇到环了,更新 id 为环内最小的 id
遍历并更新完邻接节点之后,我们判断一下一开始传入的 id 与更新后的 id 是否相同
- 如果相同则表示当前节点的子节点无法遍历到父节点(因为如果可以的话 id 应该比传入 id 更小),当前节点与父节点的链接就是关键链接
- 判断完之后返回当前节点的最终 id
- 结束 dfs 之后,返回关键链接的集合就好
C++
class Solution {
public:
vector<vector<int>> criticalConnections(int n,
vector<vector<int>> &connections) {
// 构造一个节点与其邻接节点的映射表
unordered_map<int, vector<int>> map;
buildMap(map, connections);
// id 用来表示节点是否属于同一个组(是否成环)
vector<int> groupId(n, -1);
// 存放 critical connections
vector<vector<int>> res;
dfs(0, -1, 0, groupId, map, res);
return res;
}
void buildMap(unordered_map<int, vector<int>> &map,
vector<vector<int>> &connections) {
for (auto connection : connections) {
map[connection[0]].push_back(connection[1]);
map[connection[1]].push_back(connection[0]);
}
}
int dfs(int cur, int parent, int id, vector<int> &groupId,
unordered_map<int, vector<int>> &map, vector<vector<int>> &res) {
groupId[cur] = id;
// 遍历当前的邻接节点
for (int node : map[cur]) {
if (node == parent) {
// 邻接节点是父节点直接略过
continue;
} else if (groupId[node] == -1) {
// 如果子节点没有遍历过,对其 dfs 计算子节点的 id
// 如果子节点的 id 小于当前 id,表示成环了,要更新当前 id
groupId[cur] = min(groupId[cur], dfs(node, cur, id + 1, groupId, map, res));
} else {
// 如果邻接节点有遍历过且 id 比较小,表示成环,需要更新当前 id
groupId[cur] = min(groupId[cur], groupId[node]);
}
}
// 如果传入的 id 跟计算之后的 id 相等,表示当前节点跟父节点不在同一个环内,两者之间的链接就是 critical connection
if (id == groupId[cur] && cur != 0) {
res.push_back({parent, cur});
}
return groupId[cur];
}
};
Javascript
/**
* @param {number} n
* @param {number[][]} connections
* @return {number[][]}
*/
var criticalConnections = function(n, connections) {
// 构造一个节点与其邻接节点的映射表
let map = {};
buildMap(map, connections);
// id 用来表示节点是否属于同一个组(是否成环)
let groupId = new Array(n).fill(-1);
// 存放 critical connections
let res = [];
dfs(0, -1, 0, groupId, map, res);
return res;
};
var buildMap = function(map, connections) {
for(let connection of connections) {
map[connection[0]] ? map[connection[0]].push(connection[1]) : map[connection[0]] = [connection[1]];
map[connection[1]] ? map[connection[1]].push(connection[0]) : map[connection[1]] = [connection[0]];
}
}
/**
* @param cur 当前遍历节点
* @param parent 当前遍历节点的父节点
* @param id 当前节点的默认 id
* @param groupId 存放所有节点 id 的数组
* @param map 存放所有节点与其邻接节点的映射表
* @param res 用来放 critical connections
* @return id 当前节点计算后的 id
*/
var dfs = function(cur, parent, id, groupId, map, res) {
groupId[cur] = id;
// 遍历当前的邻接节点
for(let node of map[cur]) {
if(node == parent) {
// 邻接节点是父节点直接略过
continue;
} else if(groupId[node] === -1) {
// 如果子节点没有遍历过,对其 dfs 计算子节点的 id
// 如果子节点的 id 小于当前 id,表示成环了,要更新当前 id
groupId[cur] = Math.min(groupId[cur], dfs(node, cur, id+1, groupId, map, res));
} else {
// 如果邻接节点有遍历过且 id 比较小,表示成环,需要更新当前 id
groupId[cur] = Math.min(groupId[cur], groupId[node]);
}
}
// 如果传入的 id 跟计算之后的 id 相等,表示当前节点跟父节点不在同一个环内,两者之间的链接就是 critical connection
if(groupId[cur] === id && cur!= 0) {
res.push([parent, cur]);
}
return groupId[cur];
}
Reference
https://www.bilibili.com/video/BV15t4y197eq?spm_id_from=333.337.search-card.all.click