Learnitweb

Problem 797: All Paths From Source to Target

You are given a directed acyclic graph (DAG). The graph is represented as an adjacency list, where:

graph[i] = list of all nodes you can go to directly from node i.

Your task is to return all possible paths from node 0 (source) to node n−1 (target).


Understanding the Problem

Example:

graph =
[
[1,2],
[3],
[3],
[]
]

This means:

From 0 → you can go to 1 and 2
From 1 → you can go to 3
From 2 → you can go to 3
From 3 → no outgoing edges

Possible paths from 0 to 3:

[0, 1, 3]
[0, 2, 3]

Output should be:

[
[0,1,3],
[0,2,3]
]

Because the graph is a DAG (no cycles), DFS is guaranteed to terminate.


Approach (Explained in Simple Language)

We need to explore all paths, so Depth-First Search (DFS) with backtracking is the simplest and most natural solution.

1. Start DFS from node 0

Keep a list to maintain the current path.

2. For each node, explore all its children

Append the child to the current path and continue DFS.

3. If we reach the final node (n−1), record the current path

Make a copy of the current path and add it to the result.

4. Use backtracking to remove the last node after exploring

This ensures that paths do not interfere with each other.

5. Finally, return all stored paths

DFS ensures that all possible valid paths are discovered.

Time complexity is manageable because the graph is small and acyclic.


Java Code

import java.util.*;

public class AllPathsFromSourceToTarget {

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {

        List<List<Integer>> result = new ArrayList<>();
        List<Integer> currentPath = new ArrayList<>();

        currentPath.add(0);

        dfs(graph, 0, currentPath, result);

        return result;
    }

    private void dfs(int[][] graph, int node, List<Integer> currentPath,
                     List<List<Integer>> result) {

        if (node == graph.length - 1) {
            result.add(new ArrayList<>(currentPath));
            return;
        }

        for (int next : graph[node]) {

            currentPath.add(next);

            dfs(graph, next, currentPath, result);

            currentPath.remove(currentPath.size() - 1);
        }
    }

    public static void main(String[] args) {
        AllPathsFromSourceToTarget solution = new AllPathsFromSourceToTarget();
        int[][] graph = {{1,2},{3},{3},{}};
        System.out.println(solution.allPathsSourceTarget(graph));
    }
}

Dry Run

graph =
[
[1,2],
[3],
[3],
[]
]

Start:

currentPath = [0]
result = []

Call dfs(0)


DFS at node 0

graph[0] = [1, 2]

Two branches to explore.


Branch 1 → Go to node 1

currentPath = [0, 1]

Call dfs(1)

DFS at node 1

graph[1] = [3]

Only one child → 3

currentPath = [0,1,3]

Node 3 is the target (n−1 = 3).
Add path to result:

result = [ [0,1,3] ]

Backtrack: remove 3
currentPath = [0,1]

Backtrack further: remove 1
currentPath = [0]


Branch 2 → Go to node 2

currentPath = [0, 2]

Call dfs(2)

DFS at node 2

graph[2] = [3]

Only one child → 3

currentPath = [0,2,3]

Node 3 is target → store path:

result = [ [0,1,3], [0,2,3] ]

Backtrack: remove 3
currentPath = [0,2]

Backtrack: remove 2
currentPath = [0]


DFS Complete

Queue exhausted and all branches explored.

Final result:

[ [0,1,3], [0,2,3] ]


Why This Approach Works

The graph is a DAG, so DFS will never loop infinitely.
Backtracking ensures every path is independent, avoiding conflicts.

DFS naturally explores all root-to-leaf style paths, making it perfect for this problem.

Time complexity is proportional to the number of possible paths.