All Paths from Source Lead to Destination

Updated on 15 May, 2025
All Paths from Source Lead to Destination header image

Problem Statement

In this problem, you are given a directed graph represented as a list of edges, where each edge edges[i] = [ai, bi] shows a direct link from node ai to node bi. Besides the graph itself, you are provided two specific nodes: source and destination. Your task is to determine if every possible path starting from the source node ultimately leads exclusively to the destination node under the following conditions:

  • There exists at least one valid path from the source to the destination.
  • Any potential path that leads to a node with no outward edges must terminate at the destination node.
  • The total number of different paths from the source to the destination is finite.

The solution should return true if all paths from the source adhere to these conditions leading to the destination node, otherwise false.

Examples

Example 1

Input:

n = 3, edges = [[0,1],[0,2]], source = 0, destination = 2

Output:

false

Explanation:

It is possible to reach and get stuck on both node 1 and node 2.

Example 2

Input:

n = 4, edges = [[0,1],[0,3],[1,2],[2,1]], source = 0, destination = 3

Output:

false

Explanation:

We have two possibilities: to end at node 3, or to loop over node 1 and node 2 indefinitely.

Example 3

Input:

n = 4, edges = [[0,1],[0,2],[1,3],[2,3]], source = 0, destination = 3

Output:

true

Constraints

  • 1 <= n <= 104
  • 0 <= edges.length <= 104
  • edges.length == 2
  • 0 <= ai, bi <= n - 1
  • 0 <= source <= n - 1
  • 0 <= destination <= n - 1
  • The given graph may have self-loops and parallel edges.

Approach and Intuition

Based on the provided examples and constraints, we can derive an approach to solve the task:

  1. Graph Representation: Start by representing the graph with an adjacency list. This enables easy traversal and checking outgoing edges for each node.

  2. Depth-First Search (DFS) or Breadth-First Search (BFS):

    • Use DFS or BFS to explore all possible paths from the source. This helps in understanding which nodes are accessible from the source and also to detect cycles.
    • During this traversal, also check nodes that have no outgoing edges. Ensure these nodes are the destination node. If any such node isn't, immediately return false.
  3. Cycle Detection:

    • Detect cycles in the paths using a visited stack. If a cycle is detected and it doesn't include the destination node or it prevents reaching the destination, return false.
    • If destination is part of a cycle, it must be the only node in its cycle or have a direct cycle (self-loop), such that the traversal is confined only to destination.
  4. Path Confirmation:

    • Ensure every reachable node from the source has paths leading solely to the destination. If any node can lead to an alternate path not concluding at destination, or traps in a cyclic loop not involving destination, the condition is not satisfied.
  5. Validation:

    • After traversal, if all paths lead from source to destination without inconsistencies like unreachable destination or incorrect terminations, then confirm by returning true.

Complexity: The complexity mainly depends on the number of nodes (n) and edges in the graph given the need to traverse potentially all nodes and edges. With the constraints provided, we ensure the approach remains efficient within these limits.

Solutions

  • Java
  • Python
java
class Solution {
    
    enum State { VISITING, VISITED };
    
    public boolean canReachDestination(int number_of_nodes, int[][] links, int start, int target) {
        
        List<Integer>[] adjacencyList = createGraph(number_of_nodes, links);
        return checkPath(adjacencyList, start, target, new State[number_of_nodes]);
    }
    
    private boolean checkPath(List<Integer>[] adjList, int current, int end, State[] visited) {
        
        if (visited[current] != null) {
            return visited[current] == State.VISITED;
        }
        
        if (adjList[current].isEmpty()) {
            return current == end;
        }
        
        visited[current] = State.VISITING;
        
        for (int neighbor : adjList[current]) {
            if (!checkPath(adjList, neighbor, end, visited)) {
                return false;
            }
        }
        
        visited[current] = State.VISITED;
        return true;
    }
    
    private List<Integer>[] createGraph(int nodes, int[][] connection) {
        List<Integer>[] resultGraph = new List[nodes];
        for (int i = 0; i < nodes; i++) {
            resultGraph[i] = new ArrayList<>();
        }
        
        for (int[] link : connection) {
            resultGraph[link[0]].add(link[1]);
        }
        
        return resultGraph;
    }
}

The given Java code defines a method to determine whether all possible paths from a specified source node lead to a designated destination node in a directed graph. This is performed within the Solution class, utilizing a depth-first search (DFS) algorithm enhanced with state tracking to manage node visits.

Key features of the solution:

  • Graph Representation and Initialization: A graph is represented using an adjacency list, where each node index contains a list of direct connections to other nodes. The createGraph method initializes this representation based on the input number of nodes and their connections.

  • State Tracking: Nodes in the graph are tracked using an enumeration State with values VISITING and VISITED, to prevent revisits and detect cycles or paths that might not reach the target.

  • Path Checking: The checkPath method implements the core DFS logic. If a node is already visited and labeled as VISITED, it confirms a valid path to the target already exists through this route. Nodes without outgoing links are check ends, directly comparing the current node to the target. During the traversal, it marks nodes as VISITING and upon completion of all possible routes from that node, it marks them as VISITED.

  • Overall Handling: The canReachDestination method serves as the entry point, where it creates the adjacency list for the graph, initializes the state array for nodes, and triggers the path checking from the source node to the target.

The code provides scalability and efficiency in determining valid paths in a graph structure using systematic state transitions and depth-first traversal, making it suitable for problems dealing with route validations in directed graphs.

python
class Solution:
    IN_PROGRESS = 1
    COMPLETED = 2

    def hasPath(self, node_count: int, connections: List[List[int]], start: int, end: int) -> bool:
        adjacency_list = self.createGraph(node_count, connections)
        return self.checkPath(adjacency_list, start, end, [None] * node_count)
        
    def checkPath(self, graph, current_node, target, visited_states):

        if visited_states[current_node] is not None:
            return visited_states[current_node] == Solution.COMPLETED
        
        if not graph[current_node]:
            return current_node == target
        
        visited_states[current_node] = Solution.IN_PROGRESS
        
        for neighbor in graph[current_node]:
            if not self.checkPath(graph, neighbor, target, visited_states):
                return False
        
        visited_states[current_node] = Solution.COMPLETED
        return True

    def createGraph(self, total_nodes, links):
        adj_list = [[] for _ in range(total_nodes)]
        
        for link in links:
            adj_list[link[0]].append(link[1])
            
        return adj_list

The Python3 program features the Solution class designed to determine if all paths starting from a source node can consistently lead to a destination node in a directed graph. The algorithm is structured around three key functions:

  • hasPath: Initializes the graph from the input and invokes checkPath to explore possible routes from the source to the target.
  • checkPath: Recursively explores each path and checks for cycles using a state array, ensuring paths do not re-enter 'in-progress' nodes.
  • createGraph: Constructs an adjacency list from the input connections to represent the graph.

The solution employs depth-first search (DFS) for path traversal, conducted primarily in the checkPath function. Here's a succinct overview of its approach:

  1. The graph is first converted into an adjacency list to facilitate efficient traversal of connections.
  2. A recursive DFS is guided by the checkPath function, which employs a three-state system (None, IN_PROGRESS, COMPLETED) stored in visited_states to manage the exploration status of each node:
    • None indicates the node hasn't been visited.
    • IN_PROGRESS marks nodes currently being explored, assisting in cycle detection.
    • COMPLETED denotes nodes fully processed without finding invalid paths.
  3. During the DFS, if a node has no outgoing edges, it checks if the node is the destination. If it's currently being explored and found again, a cycle is detected, and False is immediately returned.
  4. The process continues for all neighbors of the node; if any path check returns False, the function propagates this to prevent further unnecessary checks.

This method ensures that all explorations are not only efficient but also robust against cycles and incomplete paths. The efficient backtracking and state management allow the function to handle complex graphs effectively without revisiting or reprocessing completed nodes. This program thus offers a reliable way to assess path completeness in directed graphs from a given start to end node.

Comments

No comments yet.