← Home

797. All Paths From Source to Target

LeetCode article · C++ solution
Website made by wuisabel-gif · Original C++ code by keineahnung2345
prefix sumsC++Markdown
797

Let's make this one less mysterious. For 797. All Paths From Source to Target, the solution in this repository is mainly a prefix sums solution.

Guide

What?

We want to turn the problem statement into a smaller set of decisions the computer can repeat safely. Instead of trying to be clever immediately, read the code as a sequence of questions:

  • What state are we keeping?
  • How do we move from one state to the next?
  • When do we know the answer is already determined?

For this file, the main tools are: prefix sums, backtracking.

Guide

When?

This is the kind of solution you want when the problem has structure hiding inside a messy-looking input. The accepted code reduces that pressure by storing exactly the information that remains useful later.

The important function names to track are toPath, dfs.

Guide

Why?

The point of the implementation is not to make the code longer. It is to avoid doing the same thinking twice.

  • The two-dimensional vector is the memory of the solution: grid state, DP state, or adjacency shape.
  • The final return is not magic; it is the invariant after the loops or recursion have finished doing their accounting.

Guide

How?

Walk through the solution in this order:

  1. Initialize the memory or helper structure.
  2. Process candidates in the order the invariant expects.
  3. Update the answer only when the current state is valid.
  4. Return the value that represents the fully processed input.

The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.

Guide

Complexity

  • Time: O(n) to O(n log n), depending on the dominant loop or data structure operation
  • Space: O(n) in the usual case for auxiliary containers or recursion

Guide

C++ Solution

Your submission

The accepted solution

solution.cpp
01/**
02Given a directed, acyclic graph of N nodes.  Find all possible paths from node 0 to node N-1, and return them in any order.
03
04The graph is given as follows:  the nodes are 0, 1, ..., graph.length - 1.  graph[i] is a list of all nodes j for which the edge (i, j) exists.
05
06Example:
07Input: [[1,2], [3], [3], []] 
08Output: [[0,1,3],[0,2,3]] 
09Explanation: The graph looks like this:
100--->1
11|    |
12v    v
132--->3
14There are two paths: 0 -> 1 -> 3 and 0 -> 2 -> 3.
15Note:
16
17The number of nodes in the graph will be in the range [2, 15].
18You can print different paths in any order, but you should keep the order of nodes inside one path.
19**/
20
21//Your runtime beats 53.33 % of cpp submissions.
22class Solution {
23public:
24    vector<vector<int>> oneSourceTarget(vector<vector<int>>& graph, int src, vector<int>& fromPath){
25        vector<vector<int>> toPaths;
26        vector<int> fromPath2 = vector<int>(fromPath);
27        fromPath2.push_back(src);
28        
29        for(int i = 0; i < graph[src].size(); i++){
30            if(graph[src][i]!=graph.size()-1){ 
31                //not goal, then keep going
32                vector<vector<int>> results = oneSourceTarget(graph, graph[src][i], fromPath2);
33                //similar to python's toPaths.extend(results)
34                if(!results.empty()){
35                    //https://stackoverflow.com/questions/313432/c-extend-a-vector-with-another-vector
36                    toPaths.reserve(toPaths.size() + distance(results.begin(),results.end()));
37                    toPaths.insert(toPaths.end(),results.begin(),results.end());
38                }
39            }else{ 
40                //achieve goal, then prepare the result and then return
41                vector<int> toPath(fromPath2);
42                toPath.push_back(graph[src][i]);
43                toPaths.push_back(toPath);
44            }
45        }
46        //toPaths.empty() will be true if no paths to target
47        return toPaths; 
48    }
49    
50    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
51        vector<int> fromPath;
52        vector<vector<int>> toPaths;
53        
54        vector<vector<int>> curToPaths = oneSourceTarget(graph, 0, fromPath);
55            
56        // reserve() is optional - just to improve performance
57        toPaths.reserve(toPaths.size() + distance(curToPaths.begin(),curToPaths.end()));
58        toPaths.insert(toPaths.end(),curToPaths.begin(),curToPaths.end());
59        
60        return toPaths;
61    }
62};
63
64/**
65Approach #1: Recursion [Accepted]
66Intuition
67
68Since the graph is a directed, acyclic graph, any path from A to B is going to be composed of A plus a path from any neighbor of A to B. We can use a recursion to return the answer.
69
70Algorithm
71
72Let N be the number of nodes in the graph. If we are at node N-1, the answer is just the path {N-1}. Otherwise, if we are at node node, the answer is {node} + {path from nei to N-1} for each neighbor nei of node. This is a natural setting to use a recursion to form the answer.
73
74//This is the answer adpated from the solution which is originally Java version
75//Your runtime beats 48.17 % of cpp submissions.
76class Solution {
77public:
78    vector<vector<int>> solve(vector<vector<int>>& graph, int node){
79        int N = graph.size();
80        vector<vector<int>> ans;
81        if(node==N-1){
82            vector<int> path;
83            //construct the path from the end
84            path.push_back(node);
85            ans.push_back(path);
86            return ans;
87        }
88        
89        for(int i = 0; i < graph[node].size(); i++){
90            int nei = graph[node][i];
91            for(vector<int> path : solve(graph, nei)){
92                //prefix the returned path with current node
93                //the returned path includes "nei"
94                path.insert(path.begin(), node); //vector.insert() is in-place
95                ans.push_back(path);
96            }
97        }
98        
99        return ans;
100    }
101    
102    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
103        return solve(graph, 0);  
104    }
105};
106
107Complexity Analysis
108
109Time Complexity: O(2^N*N^2). We can have exponentially many paths, and for each such path, 
110our prepending operation path.add(0, node) will be O(N^2).
111
112Space Complexity: O(2^N*N), the size of the output dominating the final space complexity.
113**/
114
115//dfs
116//Runtime: 16 ms, faster than 94.16% of C++ online submissions for All Paths From Source to Target.
117//Memory Usage: 10.5 MB, less than 92.07% of C++ online submissions for All Paths From Source to Target.
118class Solution {
119public:
120    void dfs(vector<vector<int>>& graph, int cur, vector<int>& path, vector<vector<int>>& paths){
121        int n = graph.size();
122        
123        if(cur == n-1){
124            paths.push_back(path);
125        }else{
126            for(int nei : graph[cur]){
127                path.push_back(nei);
128                dfs(graph, nei, path, paths);
129                path.pop_back();
130            }
131        }
132    };
133    
134    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
135        vector<int> path = {0};
136        vector<vector<int>> paths;
137        
138        dfs(graph, 0, path, paths);
139        
140        return paths;
141    }
142};

Cost

Complexity

Time
O(n) to O(n log n), depending on the dominant loop or data structure operation
Dominated by the main traversal, recursion, or data-structure operations in the code.
Space
O(n) in the usual case for auxiliary containers or recursion
Auxiliary state plus the answer structure where the problem requires one.