leetcode
leetcode 2251 ~ 2300
图中的最短环

图中的最短环

难度:

标签:

题目描述

There is a bi-directional graph with n vertices, where each vertex is labeled from 0 to n - 1. The edges in the graph are represented by a given 2D integer array edges, where edges[i] = [ui, vi] denotes an edge between vertex ui and vertex vi. Every vertex pair is connected by at most one edge, and no vertex has an edge to itself.

Return the length of the shortest cycle in the graph. If no cycle exists, return -1.

A cycle is a path that starts and ends at the same node, and each edge in the path is used only once.

 

Example 1:

Input: n = 7, edges = [[0,1],[1,2],[2,0],[3,4],[4,5],[5,6],[6,3]]
Output: 3
Explanation: The cycle with the smallest length is : 0 -> 1 -> 2 -> 0 

Example 2:

Input: n = 4, edges = [[0,1],[0,2]]
Output: -1
Explanation: There are no cycles in this graph.

 

Constraints:

  • 2 <= n <= 1000
  • 1 <= edges.length <= 1000
  • edges[i].length == 2
  • 0 <= ui, vi < n
  • ui != vi
  • There are no repeated edges.

代码结果

运行时间: 52 ms, 内存: 16.8 MB


/*
题目思路:
1. 使用Stream API构建图的邻接表。
2. 采用广度优先搜索(BFS)查找最短环。
3. 使用OptionalInt来处理可能不存在环的情况。
*/

import java.util.*;
import java.util.stream.*;

public class ShortestCycleStream {
    public int findShortestCycle(int n, int[][] edges) {
        List<List<Integer>> graph = IntStream.range(0, n)
                                              .mapToObj(i -> new ArrayList<Integer>())
                                              .collect(Collectors.toList());
        Arrays.stream(edges).forEach(edge -> {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        });

        return IntStream.range(0, n)
                        .map(i -> bfs(i, graph, n))
                        .filter(len -> len != Integer.MAX_VALUE)
                        .min()
                        .orElse(-1);
    }

    private int bfs(int start, List<List<Integer>> graph, int n) {
        int[] dist = new int[n];
        Arrays.fill(dist, -1);
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(start);
        dist[start] = 0;

        while (!queue.isEmpty()) {
            int node = queue.poll();

            for (int neighbor : graph.get(node)) {
                if (dist[neighbor] == -1) {
                    dist[neighbor] = dist[node] + 1;
                    queue.offer(neighbor);
                } else if (dist[neighbor] >= dist[node]) {
                    return dist[neighbor] + dist[node] + 1;
                }
            }
        }

        return Integer.MAX_VALUE;
    }
}

解释

方法:

这个题解首先利用了邻接表来表示图的结构。使用并查集来检测边的添加是否会形成环。如果在添加一条边时,该边连接的两个顶点已经在同一个集合中,说明添加这条边会形成一个环。然后,题解利用广度优先搜索(BFS)来寻找最短环。搜索从某个顶点开始,尝试找到通过该点的最短环。如果在过程中发现一个顶点已经访问过(即在当前BFS层次中再次访问),则可以计算形成环的长度。题解中逐个处理每条边,检查其是否形成环,并使用BFS确定环的最短长度。对于每个已经检测过的点,为了减少计算,会从图中断开其连接,避免重复计算。

时间复杂度:

O(n^3)

空间复杂度:

O(V + E)

代码细节讲解

🦆
在题解中,为何选择使用并查集来检测环的形成,这种方法与通过邻接表直接搜索环的方法相比有什么优势或劣势?
并查集是一种高效的数据结构,用于处理元素分组和组间关系的问题,特别擅长处理动态连通性问题。使用并查集检测环的形成主要是因为其能快速判断两个节点是否属于同一个集合,即快速检测添加边是否会形成环。相比直接使用邻接表进行深度或广度优先搜索找环,这种方法在初始阶段可以更快地拒绝形成环的边,避免不必要的搜索,从而提高效率。然而,劣势在于并查集本身不存储路径信息,因此无法直接用来找到具体的环或计算环的长度,需要配合其他搜索方法使用。
🦆
题解提到在检测到环后会‘断开已检测点的连接’,这样做的目的是什么?是否可能导致某些环被错误地忽略或未能检测到?
断开已检测点的连接主要是为了优化算法性能,减少不必要的重复计算。这种做法能够确保每个节点作为环的起点最多被考虑一次,从而降低时间复杂度。然而,这种方法确实可能导致某些环未被检测到,特别是如果存在多个通过同一节点的不同环时,断开连接可能会使得后续的搜索无法找到其他环。因此,这种优化是在算法效率和可能遗漏环之间的一种权衡。
🦆
在使用BFS搜索最短环时,为什么要记录每个节点的访问距离,且在再次遇到已访问节点时立即计算环的长度?
在BFS搜索中,记录每个节点的访问距离可以帮助我们快速判断并计算环的长度。当在BFS过程中遇到一个已经访问过的节点时,这意味着我们找到了一个闭环。通过比较这个节点的距离与当前距离,可以立即计算出环的长度。这种即时计算方法是有效的,因为BFS保证了每次扩展的都是等距离的节点,所以一旦形成环,我们就可以确信找到了从起点出发的最短路径长度,即最短环。
🦆
题解中说明,如果dist[j] < d,则返回(dist[j] << 1 | 1),这个表达式的意义是什么?如何理解这个计算过程?
表达式(dist[j] << 1 | 1)是位操作的一种用法,其中'dist[j] << 1'将dist[j]的值左移一位,相当于乘以2,'| 1'则是将结果与1进行按位或操作,实际上是将结果的最低位设置为1。这在某些情况下用来调整偶数长度的环,使其变为奇数,可能是为了区分不同情况下环的长度或类型。然而,具体含义可能依赖于题解的其他部分或上下文,此处没有更多信息,难以给出完全准确的解释。

相关问题