1. 题目来源
链接:797. 所有可能的路径
2. 题目解析
一个非常简单的图遍历。有个易错点需要讨论。
本题中,给出的就是邻接表的形式,不需要额外建图。
在写法一中,是先进行 path.push_back()
,然后再 if
判断是否递达终点时,记录答案,并没有在后面跟上紧接着的 return
。 在此需要注意,加了 return
是错误的。因为此时一定访问递达了终点,path
中是包括终点的,那么如果加了 return
,返回上层在回溯的时候,就会将这个终点回溯出 path
,而不是终点的上个点,造成错误的恢复现场。
以样例为例,初始加入 0,进入下层,1,进入下层到达终点 3。记录答案 [0,1,3]
,此时应该返回上层,那么返回的应该是 0 这一层,从 0-->1
的任务已经走完了,现在应该走 0-->2
的任务,所以要将 1 也要回溯掉。
- 如果不加
return
,那么 3 会执行到末尾被pop_back
,同时 1 也会由于返回上一层而被pop_back
,最终返回path=[0]
,的初始局面,接着遍历 0 的另一个出点,即为 2 这个点,然后遍历 2 的出点,即为终点 3,记录答案。是正确的恢复现场。 - 如果加了
return
,那么遍历到 3 时,答案被记录,其中path=[0,1,3]
,然后返回至上一层,1 没有遍历对象,所以执行pop_back()
,但此时,pop_back()
的对象应该是 1,而不是path
中的末尾的 3,造成了错误的恢复现场。那么 1 就将被保留下来,此时回到初始层 0,path=[0,1]
显然已经出错了,然后遍历 0 的另一个出点 2,2 再遍历自身唯一的出点 3,走到终点,记录答案,path=[0,1,2,3]
。这显然是错误的。
那么这个易错点引发出来的问题是什么呢?
- 我们的终点不止一次被访问到,需要正确的恢复现场,并保证下一次访问时状态的正确性。
return
代表的是,遇到终点,直接结束。但是要知道,终点此时已经加入到了path
中,它本身也是需要恢复现场的,它是因为自身的下层已经没路了,所以不能再向下走了,所以要反回上层了,那么就需要恢复现场。同时,因为要通过它的恢复现场,层层向上返回,再此从另一个路径到达终点。- 这里的没路向下,故而返回是会触发后面的语句的,包括后面的恢复现场语句。然而
return
并不会。我们常见的return
语句一般用于如果继续走下去,会使后面语句出现越界访问等错误,比如常见的全排列、子集等问题。当然,对应的也有自身的恢复现场。 - 所以,在
dfs
中,如果终点不止一次被访问,那么就需要对其恢复现场,并且要对return
做以慎重考虑。
- 时间复杂度: O ( n k ) O(nk) O(nk)。
- 空间复杂度: O ( n ) O(n) O(n)
代码:
写法一:不可提前 return
。要将终点正常恢复现场清掉。
class Solution {
public:
int n;
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> g;
void dfs(int u) {
path.push_back(u);
if (u == n - 1) res.push_back(path); // 不可return
for (auto e : g[u]) dfs(e);
path.pop_back();
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
g = graph;
n = g.size();
dfs(0);
return res;
}
};
写法二:将终点特殊处理,恢复现场 pop_back()
掉再 return
。
class Solution {
public:
int n;
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> g;
void dfs(int u) {
path.push_back(u);
if (u == n - 1) {
res.push_back(path);
path.pop_back();
return ;
}
for (auto e : g[u]) dfs(e);
path.pop_back();
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
g = graph;
n = g.size();
dfs(0);
return res;
}
};
写法三:常见写法
这个写法就是常见写法了,dfs
前记录状态,后紧接着就是恢复现场,一开始进来就是判断边界、记录答案。
这个就并没有斩断状态记录到恢复现场的这个过程,return
后返回上层,直接进行恢复现场。但是要提前将初始点 0 加入到 path
中。
class Solution {
public:
int n;
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> g;
void dfs(int u) {
if (u == n - 1) {
res.push_back(path);
return ;
}
for (auto e : g[u]) {
path.push_back(e); // 每次将下一个出点提前加入到path中
dfs(e);
path.pop_back();
}
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
g = graph;
n = g.size();
path.push_back(0); // 先将 0 加入进 path 中
dfs(0);
return res;
}
};
写法四:bfs 板子题,需要写的精简。
- 实际上当前点可以通过队列中的最后一个元素进行获取到。这样就不用弄一个 pair 存当前点和路径信息了。
- 路径信息只用一个 auto path 即可,值传递过去的,都是一份拷贝值,里面是互不影响的。不用再自己申请一份 vector 进行赋值一下了。
class Solution {
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> res;
int n = graph.size();
queue<vector<int>> q;
q.push({0});
while (q.size()) {
auto path = q.front(); q.pop();
int cur = path.back();
for (int &x : graph[cur]) {
path.push_back(x);
if (x == n - 1) {
res.push_back(path);
} else {
q.push(path);
}
path.pop_back();
}
}
return res;
}
};