[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. 字符串的排列(全排列+枚举顺序+组合类型枚举+知识理解+模板题)

LeetCode 797. 所有可能的路径

LeetCode 797. 所有可能的路径

LeetCode Algorithm 797. 所有可能的路径

LeetCode Algorithm 797. 所有可能的路径