All Paths From Source to Target Solution
Given a directed acyclic graph graph where node 0 is the source and node n - 1 is the target, return every path from source to target.
Problem Statement
You are given a graph as an adjacency list where graph[i] contains all nodes reachable from node i. The graph is guaranteed to be a DAG (directed acyclic graph). Return all paths that start at 0 and end at n - 1.
DFS with Path Copying โ O(P ยท Lยฒ)
Start DFS from node 0. For every outgoing edge, create a new copy of the current path, append the next node, and recurse. Whenever you reach target = n - 1, append that full path to the answer.
Because the graph is acyclic, every recursive branch eventually stops. Copying the path for each branch isolates state, so sibling branches never interfere with one another.
L, you may copy prefixes of length 1, 2, 3, ... , L, which becomes O(Lยฒ) overhead per full path. We can reuse one shared path list and only copy when we actually emit a complete answer.
| Metric | Value |
|---|---|
| Time | O(P ยท Lยฒ) in the worst case due to repeated path copying |
| Space | O(L) recursion stack, excluding output |
Backtracking DFS with One Shared Path โ O(P ยท L)
Keep one mutable path list. Start with [0]. When exploring an edge node โ next, append next, recurse, then pop it after returning. Only when we reach the target do we copy the path into the answer list.
- Initialize: set
target = n - 1,path = [0], andresult = []. - DFS(node): if
node == target, copypathintoresultand return. - Explore neighbors: for each
nextingraph[node], append it topath, recurse, then remove it after returning. - No visited set needed: the graph is a DAG, so DFS cannot loop forever.
- Copy only at leaves: never store the live
pathobject directly inresult.
- If the problem asks for all paths, all combinations, or all valid sequences, DFS + backtracking is usually the right pattern.
- For this problem specifically, the word DAG is a huge clue that recursion can safely enumerate every branch without cycle handling.
- In a general directed graph, a visited/path-state structure is necessary to avoid infinite recursion on cycles.
- But LC 797 guarantees acyclicity, so a visited set would be extra work and can even be conceptually misleading.
- The only state we need is the current path.
Visual Walkthrough
Trace the DFS on graph = [[1,2],[3],[3],[]]. There are exactly two source-to-target paths.
Code โ Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| DFS + copied path each branch | O(P ยท Lยฒ) | O(L) recursion, excluding output | Simple to reason about, but wastes time re-copying path prefixes. |
| Backtracking DFS โ optimal | O(P ยท L) | O(L) recursion, excluding output | Only copies the path once per completed answer, which matches the unavoidable output size. |
Pis the number of valid source-to-target paths, andLis the maximum path length.- Since the output itself contains P paths of length up to L, any correct solution must spend at least O(P ยท L) time just to write the answer down.
Edge Cases & Pitfalls
| Case | Expected behavior | Why it matters |
|---|---|---|
| Single direct path | [[1],[]] โ [[0,1]] | The base case is reached immediately after one recursive step. |
| Multiple branches | Return every valid path | DFS must continue exploring after recording the first answer. |
| Dead-end node before target | That branch returns without adding anything | Not every path prefix necessarily reaches n - 1. |
| No visited set | Still correct for LC 797 | The graph is guaranteed to be acyclic, so recursion cannot loop forever. |
| Store live path reference | Wrong / mutated answers | You must copy path at the target, or all stored results will later change together. |
result.add(path) in Java or result.append(path) in Python at the target. That stores the same mutable list object. After backtracking pops elements, every stored answer changes too. Always save a copy: new ArrayList<>(path) or path[:].