[Mdfs] lc797. 所有可能的路径(图遍历+dfs易错点+bfs板子+知识理解)

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;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值