Distance to a Cycle in Undirected Graph

Updated on 23 May, 2025
Distance to a Cycle in Undirected Graph header image

Problem Statement

In this problem, you are provided with an integer n indicating the number of nodes in a connected undirected graph that contains exactly one cycle. Each node in this graph is uniquely numbered from 0 to n-1. Additionally, you are given an array edges where each entry edges[i] contains a pair [node1i, node2i] showing that there is a bidirectional edge connecting the nodes node1i and node2i.

The task is to determine the minimum number of edges required to travel from each node i to any node that is part of the cycle. Your solution should return this information in the form of an array answer of size n, where answer[i] represents the minimum distance from node i to the nearest node in the cycle.

Examples

Example 1

Input:

n = 7, edges = [[1,2],[2,4],[4,3],[3,1],[0,1],[5,2],[6,5]]

Output:

[1,0,0,0,0,1,2]

Explanation:

The nodes 1, 2, 3, and 4 form the cycle.
The distance from 0 to 1 is 1.
The distance from 1 to 1 is 0.
The distance from 2 to 2 is 0.
The distance from 3 to 3 is 0.
The distance from 4 to 4 is 0.
The distance from 5 to 2 is 1.
The distance from 6 to 2 is 2.

Example 2

Input:

n = 9, edges = [[0,1],[1,2],[0,2],[2,6],[6,7],[6,8],[0,3],[3,4],[3,5]]

Output:

[0,0,0,1,2,2,1,2,2]

Explanation:

The nodes 0, 1, and 2 form the cycle.
The distance from 0 to 0 is 0.
The distance from 1 to 1 is 0.
The distance from 2 to 2 is 0.
The distance from 3 to 1 is 1.
The distance from 4 to 1 is 2.
The distance from 5 to 1 is 2.
The distance from 6 to 2 is 1.
The distance from 7 to 2 is 2.
The distance from 8 to 2 is 2.

Constraints

  • 3 <= n <= 105
  • edges.length == n
  • edges[i].length == 2
  • 0 <= node1i, node2i <= n - 1
  • node1i != node2i
  • The graph is connected.
  • The graph has exactly one cycle.
  • There is at most one edge between any pair of vertices.

Approach and Intuition

Given the nature of the problem—finding distances in a graph with exactly one cycle—the plan involves several distinct steps to arrive at the solution:

  1. Identify the Cycle: Since the graph is guaranteed to have exactly one cycle and no node is isolated due to the graph being connected, we can use a Depth First Search (DFS) or Breadth-First Search (BFS) to locate this cycle. During the traversal, if a node is reached that is already visited and is not the parent of the current node, a cycle is detected.

  2. Calculate Cycle Distances: Upon detection of the cycle, we can mark all nodes that are part of the cycle. The distance of these nodes to themselves (as part of the cycle) is 0.

  3. Propagate Minimum Distances: Starting from each node identified as part of the cycle, perform a breadth-first search to propagate the distance to all other nodes. The BFS ensures that the shortest path in an unweighted graph (like ours, where each edge has the same weight) is found from the cycle nodes to all other nodes.

  4. Output the Solution: Collect and return the distances calculated as an array, where each index corresponds to the respective node and the value at that index represents the minimum distance to the cycle.

This method is efficient due to the linear relationship between nodes and edges (given by n and n in constraints) and the use of BFS, which is well-suited for shortest-path calculations in unweighted graphs. By identifying the cycle first and then spreading out to calculate distances, the approach directly addresses the problem's requirements while adhering to the constraints provided.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    vector<int> findDistances(int nodesCount, vector<vector<int>>& connections) {
        vector<bool> partOfCycle(nodesCount, true),
            expanded(nodesCount, false); // Initial states for all nodes
        vector<int> connectivity(nodesCount, 0), resultDistances(nodesCount);
        vector<vector<int>> links(nodesCount, vector<int>());

        // Create a list of links and calculate degrees of connectivity
        for (auto connection : connections) {
            links[connection[0]].push_back(connection[1]);
            links[connection[1]].push_back(connection[0]);
            connectivity[connection[0]]++;
            connectivity[connection[1]]++;
        }

        queue<int> processingQueue;

        // Identify all external nodes (degree 1)
        for (int node = 0; node < nodesCount; node++) {
            if (connectivity[node] == 1) {
                processingQueue.push(node);
            }
        }

        // BFS to identify cycle nodes by removing nodes with single connections
        while (!processingQueue.empty()) {
            int currentNode = processingQueue.front();
            processingQueue.pop();
            partOfCycle[currentNode] = false; // Node is not part of any cycle

            // Decrement the degree of nodes connected to current and enqueue if isolated
            for (auto adjacent : links[currentNode]) {
                connectivity[adjacent]--;
                if (connectivity[adjacent] == 1) {
                    processingQueue.push(adjacent);
                }
            }
        }

        // Queue cycle nodes for distance calculations
        for (int node = 0; node < nodesCount; node++) {
            if (partOfCycle[node]) {
                processingQueue.push(node);
                expanded[node] = true;
            }
        }

        // Use BFS to calculate distances from the nodes in cycles
        int layerDistance = 0;
        while (!processingQueue.empty()) {
            int layerSize = processingQueue.size(); // Number of nodes at current layer
            for (int i = 0; i < layerSize; i++) {
                int currentNode = processingQueue.front();
                processingQueue.pop();
                
                resultDistances[currentNode] = layerDistance; // Set distance for nodes
                
                // Enqueue neighboring nodes for next layer, avoid returning to previous
                for (auto neighbor : links[currentNode]) {
                    if (expanded[neighbor]) continue;
                    processingQueue.push(neighbor);
                    expanded[neighbor] = true;
                }
            }
            layerDistance++; // Increase the distance for the next layer of BFS
        }

        return resultDistances;
    }
};

The described C++ solution implements the calculation of the shortest distance from each node to a cycle within an undirected graph. It comprises an intricate algorithm combining aspects of graph theory, specifically Breadth First Search (BFS), and a queueing mechanism for processing nodes.

  • The solution class contains a single function, findDistances(), that takes two parameters: nodesCount which represents the total number of nodes in the graph, and connections, a 2D vector where each subvector represents a bidirectional edge between two nodes.

In executing this function, the following steps take place:

  1. Initialize various vectors to manage the state and connectivity of nodes, such as partOfCycle which identifies whether a node is part of a cycle or not, expanded to know if a node has been expanded in BFS, connectivity to manage connection degrees, and links to store adjacency lists for each node.

  2. Utilize the connections input to populate the links vector and calculate the degree of connectivity for each node. This preparation allows for a more efficient operation during the BFS process.

  3. Identify all external nodes that have only one connection and push them onto a processingQueue, thereby setting up the initial state of BFS.

  4. Execute a BFS process to probe and identify all nodes that are part of a cycle by effectively ‘peeling’ the graph's outer layers, affecting nodes with a single connection repeatedly. This reduction continues until only cyclic or interconnected nodes remain.

  5. For nodes determined to be part of a cycle, reinitialize and prepare the processingQueue and expanded state array to calculate the distances using another BFS iteration.

  6. During the distance calculation BFS, iterate over each node layer by layer, increasing the layerDistance progressively to ensure correct distance measurement from cycle nodes to non-cycle nodes. This BFS segregates distance tracking by processing only non-expanded neighboring nodes, thus ensuring efficiency and accuracy by avoiding repeat calculations.

  7. Return the resultant distances for each node, encapsulated in the resultDistances vector which provides a direct distance measure from every node to the nearest cycle in the graph.

This solution is comprehensive and efficiently handles the complexity of determining node distances in relation to graph cycles, leveraging BFS and effective queue management.

java
class Solution {

    public int[] getDistancesToCycles(int nodeCount, int[][] edgeList) {
        boolean[] cycleFlags = new boolean[nodeCount];
        Arrays.fill(cycleFlags, true);
        boolean[] seen = new boolean[nodeCount];
        int[] nodeDegrees = new int[nodeCount];
        int[] resultDistances = new int[nodeCount];
        List<List<Integer>> graph = new ArrayList<>();

        for (int i = 0; i < nodeCount; i++) {
            graph.add(new ArrayList<>());
        }

        for (int[] edge : edgeList) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
            nodeDegrees[edge[0]]++;
            nodeDegrees[edge[1]]++;
        }

        Queue<Integer> queue = new LinkedList<>();

        for (int i = 0; i < nodeCount; i++) {
            if (nodeDegrees[i] == 1) {
                queue.add(i);
            }
        }

        while (!queue.isEmpty()) {
            int node = queue.poll();
            cycleFlags[node] = false;

            for (int neighbor : graph.get(node)) {
                nodeDegrees[neighbor]--;
                if (nodeDegrees[neighbor] == 1) {
                    queue.add(neighbor);
                }
            }
        }

        for (int i = 0; i < nodeCount; i++) {
            if (cycleFlags[i]) {
                queue.add(i);
                seen[i] = true;
            }
        }

        int distLevel = 0;
        while (!queue.isEmpty()) {
            int breadth = queue.size();
            for (int i = 0; i < breadth; i++) {
                int node = queue.poll();
                resultDistances[node] = distLevel;

                for (int neighbor : graph.get(node)) {
                    if (seen[neighbor]) continue;
                    queue.add(neighbor);
                    seen[neighbor] = true;
                }
            }
            distLevel++;
        }

        return resultDistances;
    }
}

This Java solution finds the shortest distance from each node in an undirected graph to a cycle within the same graph. The approach involves a two-phase breadth-first search (BFS).

  • Step 1: Identify Nodes on Cycles The solution creates a graph representation and calculates the degree of each vertex. It uses a queue to execute a BFS, starting with nodes of degree 1 (leaf nodes). Nodes not part of a cycle are identified and marked during this BFS by continuously stripping leaf nodes, leaving the cycle nodes indegree more than 1.

  • Step 2: Calculate Shortest Distances from Cycle In the second phase, another BFS starts from the detected cycle nodes (nodes with cycleFlags still true). For these nodes, set their distances to zero, as they are already on the cycle. The BFS expands outward from these cycle nodes to calculate the distance to the closest cycle node for all other nodes. This is achieved by visiting all unvisited neighboring nodes and assigning them a distance incremented by one more than the current node’s distance.

  • Setup and Execution

    • Graph Representation: Nodes are stored in an adjacency list.
    • Tracking State: Arrays are utilized for keeping track of seen nodes, node degrees, and nodes present in a cycle.
    • Distance Calculation: BFS is used both to isolate the cycle components and then to spread out from them, computing distances.

The result is stored in resultDistances, where each index corresponds to a node, and the value at that index represents the minimum distance of that node to the nearest cycle in the graph. This technique efficiently identifies cycles and computes distances using classical graph traversal methods.

python
class Solution:
    def distanceFromCycle(self, node_count, connections):
        cycle_member = [True] * node_count
        has_seen = [False] * node_count
        link_count = [0] * node_count
        node_distance = [0] * node_count
        graph = [[] for _ in range(node_count)]

        # Construct the graph and establish the degree of each node
        for edge in connections:
            graph[edge[0]].append(edge[1])
            graph[edge[1]].append(edge[0])
            link_count[edge[0]] += 1
            link_count[edge[1]] += 1

        process_queue = deque()

        # Enqueue all nodes with one connection
        for i in range(node_count):
            if link_count[i] == 1:
                process_queue.append(i)

        # Breadth-First Search to find leaf nodes and retract
        while process_queue:
            vertex = process_queue.popleft()
            cycle_member[vertex] = False

            # Reduce the degree and enqueue if turning into a leaf
            for adjacent in graph[vertex]:
                link_count[adjacent] -= 1
                if link_count[adjacent] == 1:
                    process_queue.append(adjacent)

        # Refill the queue with detected cycle nodes setting visited
        for vertex in range(node_count):
            if cycle_member[vertex]:
                process_queue.append(vertex)
                has_seen[vertex] = True

        # Calculate distances using BFS from cycle nodes
        level_dist = 0
        while process_queue:
            level_size = len(process_queue)
            for _ in range(level_size):
                vertex = process_queue.popleft()
                node_distance[vertex] = level_dist

                # Visit and enqueue all adjacent nodes
                for adjacent in graph[vertex]:
                    if not has_seen[adjacent]:
                        process_queue.append(adjacent)
                        has_seen[adjacent] = True
            level_dist += 1

        return node_distance

The provided Python code calculates the distance of each node from the nearest cycle in an undirected graph. The process involves several steps:

  1. First, initialize the graph representation and arrays to track node properties such as whether it belongs to a cycle (cycle_member), if it has been seen during traversal (has_seen), the number of connections or degree (link_count), and the distance to the nearest cycle (node_distance).

  2. Construct the graph using the given connections. For each connection, update the adjacency list and increment the degree for the involved nodes.

  3. A BFS approach is applied starting with leaf nodes (nodes with only one connection). Nodes with one link are queued initially.

  4. In a BFS manner, leaf nodes are processed to retract and update their respective adjacent nodes. This identifies the nodes that are members of cycles (those that remain with more than one connection after all possible leaf nodes are retracted).

  5. Once cycle members are identified, a second BFS is used to calculate the shortest distance of all nodes from any cycle node. Each node's distance is updated appropriately as the BFS progresses level by level.

  6. It returns an array where each index corresponds to a node in the graph, and the value at that index represents the distance of that node from the nearest cycle.

This solution effectively leverages BFS to both identify cycle members and calculate distances in a succinct and efficient manner using a series of list and queue operations.

Comments

No comments yet.