797. All Paths From Source to Target
On LeetCode ->Problem¶
Given a DAG as adjacency lists, return every directed path from node 0 to node n - 1 in any order.
Key trick¶
Use DFS backtracking: keep one current path, append a neighbor before recursion, and remove it afterward. Since the graph is acyclic, no visited set is needed.
Trap¶
- A global visited set incorrectly removes valid paths that converge at the same node.
- Store a copy of the path at the target; otherwise later backtracking mutates the stored result.
- Do not stop after finding the first path.
Why is it interesting?¶
It is a compact example of output-sensitive enumeration and shows when backtracking does not require cycle detection.
Python solution¶
class Solution:
def allPathsSourceTarget(self, graph: list[list[int]]) -> list[list[int]]:
target = len(graph) - 1
res = []
path = [0]
def dfs(node):
if node == target:
res.append(path[:])
return
for nxt in graph[node]:
path.append(nxt)
dfs(nxt)
path.pop()
dfs(0)
return res
- Time: \(O(Q + L)\), where \(Q\) is the number of explored path-prefix nodes and \(L\) is the total size of the returned paths; worst case \(O(n2^n)\).
- Auxiliary space: \(O(n)\) excluding the output.
- Output space: \(O(L)\).
Comment on my solution¶
- Your reverse BFS is correct: building predecessor lists lets the search consider only nodes that can lead to the target.
- Duplicate handling is unnecessary because unique edges and the DAG property make each sequence of vertices a distinct path.
- The level-sized loop does not affect the result and can be removed because paths need not be grouped by length.
path[:]is unnecessary when adding tores, since that path object is never mutated.[prev] + pathcopies the whole partial path at every step, increasing allocation costs; DFS backtracking reuses one list and copies only completed paths.- Building predecessor lists also uses \(O(n + E)\) additional space, while forward DFS can traverse the provided adjacency lists directly.
# Ideas:
# - reachability problem
# - as we have to enumerate:
# - maybe dfs backtracking (starting at node 0)
# - must be careful to not include duplicates in output
# - would be great to not compute several time suffixes
# - maybe start from the end, the node n - 1 and build path backward
# - if we reach 0, include the path
# - this way it seems that paths in output will be unique
from collections import deque
# WORKS
class Solution:
def allPathsSourceTarget(self, graph: list[list[int]]) -> list[list[int]]:
n = len(graph)
predecessors = [[] for _ in range(n)]
for src in range(n):
for dst in graph[src]:
predecessors[dst].append(src)
res = []
q = deque([(n - 1, [n - 1])]) # (node, path node to n - 1)
while q:
for _ in range(len(q)):
node, path = q.popleft()
if node == 0:
res.append(path[:])
continue
for prev in predecessors[node]:
q.append((prev, [prev] + path))
return res