携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
每日刷题 2022.07.31
- leetcode原题链接:leetcode.cn/problems/lo…
- 难度:困难
- 方法:拓扑排序
题目
- 给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。
- 图用一个大小为 n 下标从 0 开始的数组 edges 表示,节点 i 到节点 edges[i] 之间有一条有向边。如果节点 i 没有出边,那么 edges[i] == -1 。
- 请你返回图中的 最长 环,如果没有任何环,请返回 -1 。
- 一个环指的是起点和终点是 同一个 节点的路径。
示例
- 示例1
输入: edges = [3,3,4,2,3]
输出去: 3
解释: 图中的最长环是:2 -> 4 -> 3 -> 2 。
这个环的长度为 3 ,所以返回 3 。
- 示例2
输入: edges = [2,-1,3,1]
输出: -1
解释: 图中没有任何环。
提示
- n == edges.length
- 2 <= n <= 10^5
- -1 <= edges[i] < n
- edges[i] != i
解题思路
- 初次学习拓扑排序:在图论中,一个有向无环图必然存在至少一个拓扑序与之对应,反之亦然。
- 类似的题目802. 找到最终的安全状态,作完本题后,可以再练练这道题。
- 拓扑排序的实现,是借助
BFS
的思想。首先先统计有向图中的每个节点的入度,将入度为0
的节点放入到队列中;每次从队列中取出一个节点cur
,对cur
所有相连的节点,入度都-1
(等价于:去掉cur
与其相连的边),最后将节点cur
从队列中删除,再次将图中入度为0
的节点加入到队列中,重复以上的操作。直到队列为空。 - 如果最后队列为空,有向图中的所有的节点都被遍历过,那么该有向图无环,否则存在环。
AC
代码
/**
* @param {number[]} edges
* @return {number}
*/
var longestCycle = function(edges) {
// 使用拓扑排序,不是很难,下次也就可以写了,加油
// 首先统计每个节点,所对应的入度
let n = edges.length, indeg = new Array(n).fill(0);
for(let i = 0; i < n; i++) {
if(edges[i] == -1) continue;
indeg[edges[i]]++;
}
// console.log(indeg)
// 之后通过拓扑排序将不属于环中的节点排除
let vis = new Set(), queue = [];
for(let i = 0; i < n; i++) {
if(indeg[i] === 0) {
// 需要存放到队列中
queue.push(i);
}
}
while(queue.length != 0) {
// 进行拓扑排序
let cur = queue.pop(), next = edges[cur];
indeg[next]--;
if(indeg[next] == 0) {
queue.push(next);
}
}
let flag = true;
indeg.forEach(val => {
if(val != 0) {
flag = false;
}
})
if(flag) return -1;
// console.log(vis)
// 拓扑排序完成后,所有的非环的节点都被放入到vis数组中
let max = 0, r = -1;
for(let i = 0; i < n; i++) {
if(vis.has(i) || indeg[i] == 0) {
// 表示不是环中的节点
continue;
}
// 环中的节点
let ans = dfs(i);
if(ans > max) {
max = ans;
r = i;
}
}
return max;
function dfs(tar) {
let q = [tar], cen = 0;
while(true) {
let cur = q.pop(), next = edges[cur];
if(vis.has(next)) {
// 找到环的头了
// console.log(vis, cen)
return cen;
}
q.push(next);
vis.add(next);
cen++;
}
}
};