[Mdfs] lc797. 所有可能的路径(图遍历+dfs易错点+知识理解)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Mdfs] lc797. 所有可能的路径(图遍历+dfs易错点+知识理解)相关的知识,希望对你有一定的参考价值。
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;
}
};
以上是关于[Mdfs] lc797. 所有可能的路径(图遍历+dfs易错点+知识理解)的主要内容,如果未能解决你的问题,请参考以下文章
[Mdfs] lc90. 子集 II(组合类型枚举+多重背包+去重经典)
[Mdfs] lc剑指 Offer 38. 字符串的排列(全排列+枚举顺序+组合类型枚举+知识理解+模板题)