Given the edges of a directed graph where edges[i] = [aᵢ, bᵢ] indicates there is an edge between nodes aᵢ and bᵢ, and two nodes source and destination of this graph, determine whether or not all paths starting from source eventually, end at destination, that is:
source node to the destination nodesource node to a node with no outgoing edges, then that node is equal to destination.source to destination is a finite number.Return true if and only if all roads from source lead to destination.
Example 1:
Input: n = 3, edges = [[0,1],[0,2]], source = 0, destination = 2
Output: falseExplanation: 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: falseExplanation: 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: trueConstraints:
1 <= n <= 10⁴0 <= edges.length <= 10⁴edges.length == 20 <= aᵢ, bᵢ <= n - 10 <= source <= n - 10 <= destination <= n - 1class Solution:
# We don't use the state WHITE as such anywhere. Instead, the "null" value in the states array below is a substitute for WHITE.
GRAY = 1
BLACK = 2
def leadsToDestination(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
graph = self.buildDigraph(n, edges)
return self.leadsToDest(graph, source, destination, [None] * n)
def leadsToDest(self, graph, node, dest, states):
# If the state is GRAY, this is a backward edge and hence, it creates a Loop.
if states[node] != None:
return states[node] == Solution.BLACK
# If this is a leaf node, it should be equal to the destination.
if len(graph[node]) == 0:
return node == dest
# Now, we are processing this node. So we mark it as GRAY.
states[node] = Solution.GRAY
for next_node in graph[node]:
# If we get a `false` from any recursive call on the neighbors, we short circuit and return from there.
if not self.leadsToDest(graph, next_node, dest, states):
return False
# Recursive processing done for the node. We mark it BLACK.
states[node] = Solution.BLACK
return True
def buildDigraph(self, n, edges):
graph = [[] for _ in range(n)]
for edge in edges:
graph[edge[0]].append(edge[1])
return graphTime complexity:
Space complexity:
Where represents the number of vertices in the graph and represents the number of edges in the graph.
From this Stack Overflow answer:
A BFS could be reasonable if the graph is undirected (be my guest at showing an efficient algorithm using BFS that would report the cycles in a directed graph!), where each cross edge defines a cycle (edge going from a node to an already visited node). If the cross edge is
{v1, v2}, and the root (in the BFS tree) that contains those nodes isr, then the cycle isr ~ v1 - v2 ~ r(~ is a path, - a single edge), which can be reported almost as easily as in DFS.The only reason to use a BFS would be if you know your (undirected) graph is going to have long paths and small path cover (in other words, deep and narrow). In that case, BFS would require proportionally less memory for its queue than DFS' stack (both still linear of course).
In all other cases, DFS is clearly the winner.