All Paths From Source to Target
Problem
0..n-1 is given as an adjacency
list: graph[i] lists the nodes reachable from i by one edge.
Return every path that starts at node 0 and ends at node
n - 1, each as the list of visited nodes. The paths may be returned in any
order.graph = [[1,2],[3],[3],[]]Output[[0,1,3],[0,2,3]]graph = [[4,3,1],[3,2,4],[3],[4],[]]Output[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]2 ≤ n ≤ 15 · graph[i] has distinct values, no self-loops · the graph is a DAG
Try it yourself
class Solution {
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
}
}
n - 1, and undo the last step on the way back.Instinct Connected regions, all paths, reachability, cloning or 2-colouring a graph or grid: dive from a node, mark it, recurse into unvisited neighbours.
Memory hack Mark before you march: go deep, paint as you go, and back up only when every door is already painted.
Open the Graph DFS guide →Approaches
Brute ForceBFS over Partial Paths
O(2n · n)Space O(2n · n)Put the path [0] in a queue. Repeatedly take a path, and for each
neighbour of its last node create a copy extended by that neighbour; copies
ending at n - 1 are answers, the rest go back into the queue. Since
the graph is acyclic, the process stops.
public List<List<Integer>> allPathsSourceTarget(int[][] graph) { int target = graph.length - 1; List<List<Integer>> res = new ArrayList<>(); Queue<List<Integer>> queue = new ArrayDeque<>(); queue.offer(new ArrayList<>(List.of(0))); while (!queue.isEmpty()) { List<Integer> path = queue.poll(); int last = path.get(path.size() - 1); for (int next : graph[last]) { List<Integer> extended = new ArrayList<>(path); // copy per edge extended.add(next); if (next == target) res.add(extended); else queue.offer(extended); } } return res; }
Correct and iterative, but every extension copies a path and a whole frontier of paths sits in memory at once.
OptimalDFS Backtracking with One Shared Path
O(2n · n)Space O(n) + outputWalk depth-first, appending each node to a single path list and
removing it when the call returns. Only when the walk reaches n - 1
is the path copied into the answer. There can be up to 2n-2 paths of
length up to n, so the output size bounds any algorithm; this one adds only O(n)
working memory.
public List<List<Integer>> allPathsSourceTarget(int[][] graph) { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); path.add(0); dfs(graph, 0, path, res); return res; } private void dfs(int[][] graph, int node, List<Integer> path, List<List<Integer>> res) { if (node == graph.length - 1) { res.add(new ArrayList<>(path)); // snapshot a finished path return; } for (int next : graph[node]) { path.add(next); dfs(graph, next, path, res); path.remove(path.size() - 1); // backtrack } }
Output-optimal time with just the recursion stack and one path as extra space.
onPath boolean
array — mention that the DAG guarantee is what lets you drop it.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | BFS over Partial Paths | O(2n · n) | O(2n · n) |
| Optimal | DFS Backtracking with One Shared Path | O(2n · n) | O(n) + output |
- Adding
pathitself to the result instead of a copy — every entry ends up empty. - Forgetting to remove the last node after the recursive call (no backtrack).
- Starting with an empty path — node
0must be the first element.
- Point out that output can be exponential, so no algorithm beats O(2n·n).
- Explain why the DAG property removes the need for a visited set.
- Compare memory: BFS stores a frontier of full paths, DFS stores one path.