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:
- Initialize the memory or helper structure.
- Process candidates in the order the invariant expects.
- Update the answer only when the current state is valid.
- 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
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