用DFS+memo和BFS分别解答 leetcode 847题:Shortest Path Visiting All Nodes

本文介绍了解决LeetCode 847题——最短路径遍历所有节点的方法,通过DFS+memo和BFS两种策略。由于直接使用DFS会导致超时,因此引入记忆化搜索优化。同时,BFS在求最短路径问题上效率更高。文章详细阐述了两种方法的思路,并提供了相应的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天做leetcode的一道新题847题,顺便复习了下DFS和BFS这两种图论遍历方法,用其进行解题,加深了理解。

这道题是非常经典的遍历题,直接用DFS会超时,并且无法通过所有的案例,用DFS+memo可以解决,BFS的效率更高,因为这道题本身就是求最短路径类的题目。

先来看题目描述:

二维数组给出了连通的无向图中每个节点的邻接节点列表,因此二维数组的行数是图中节点的个数。图中节点从0到n-1进行编号。求出遍历所有节点的最小路径,节点可以重复访问,边也可以重复走。


因为图的节点个数不超过12个,因此,我们完全可以用一个整数的比特位来表示该节点当前有没有被访问,这个整数就是访问的status,用二维数组,第一维就是status,第二维就是当前访问的节点编号。(status, i)表示当前访问节点i,访问状态为status的即时状态。以样例中第一个为例,输入[[1,2,3],[0],[0],[0]]。我们在这画出状态转移的树形图,方便我们解题。下面我们可以看到,我们既可以用这个树形图来写出dfs+memo的代码,也可以用它来写出bfs的代码。取决于我们对于题目的分析和对下面这张图的解读。


方法一:DFS+memo:一个bool数组visited用来剪枝,DFS路径上遇到之前已经访问过的状态的时候,就不继续进行DFS遍历了,终止(END),另一个数组mp为记忆数组,mp[(status,i)]表示(status,i)距离目标状态的最小距离,避免重复计算。在DFS的上层函数中,依次从图的各个节点开始进行DFS,求出从每个节点开始进行遍历而得到的最小遍历距离,再从这些距离中找到全局最小距离即可。

代码如下:

class Solution {
public:
    //DFS+memo
    int shortestPathLength(vector<vector<int>>& graph) 
    {
        map<pair<int,int>, int> mp; //记录当前状态到目标的最小转化步数
        vector<vector<bool>> visited(1<<graph.size(),vector<bool>(graph.size(),false)); //访问状态数组,访问过的状态就不再访问了
        int n=graph.size();
        int globalMin=INT_MAX;
        for(int i=0;i<n;i++)
        {
            pair<int,int> temp;
            temp.first=1<<i;
            temp.second=i;
            globalMin=min(globalMin,dfs(graph,mp,temp,visited)); //求出全局最小遍历距离
        }
        return globalMin;
    }
    int dfs(vector<vector<int>> &graph, map<pair<int,int>,int> &mp, pair<int,int> cur, vector<vector<bool> >&visited)
    {
        if(cur.first+1==(1<<(graph.size()))) //base case,终点状态
        {
            mp[cur]=0;
            return 0;
        }      
        if(mp.count(cur)) return mp[cur];  //如果已经求出cur距离目标的最小距离,直接将其返回就好
        int mn=INT_MAX;
        for(auto j:graph[cur.second]) //每层的分支就是节点cur.second的各个邻接节点
        {
            auto temp=cur;
            cur.first=cur.first | (1<<j);  //状态转移
            cur.second=j; //状态转移
            if(!visited[cur.first][cur.second])  //转移之后的状态没有被访问过
            {
                 visited[cur.first][cur.second]=true;  //访问转移之后的状态
                 int dist=dfs(graph,mp,cur,visited);  //用DFS求从转移之后的状态到目标状态的最小距离。
                 if(dist!=-1) mn=min(dist+1,mn);     //求得的距离不为-1,更新最小距离
                 visited[cur.first][cur.second]=false;  //状态返回转移之前,继续从cur到下一个邻接节点的状态转移
            }
            cur=temp;  //状态返回转移之前,继续从cur到下一个邻接节点的状态转移
        }
        if(mn==INT_MAX) return -1;  //如果从cur状态开始向目标状态转移的过程中,cur的邻接状态全部被访问过了,则该从该路径走肯定不是最短距离,那么mn还是INT_MAX,此时返回-1.
        mp[cur]=mn;  //求出了从cur状态到目标状态的最小转移距离
        return mp[cur]; //向上递归,返回从cur状态到目标状态的最小转移距离,兼具记忆数组的功能,避免重复计算,因为倘若在之前的其它分支先求出cur到目标节点的最小距离,在本分支再遇到该状态cur,就直接返回之前计算的结果,而不需要再递归调用和计算。
    }
};

方法二:一次BFS就能得到最终结果。该方法还是参照上面那张图,先把第一层的初始状态全部放到队列里面去,接着进行BFS遍历,等找到目标状态时返回对应的层数就好了。当然这个过程中还是会用到一个辅助数组dis[status][i],这个数组表示的意义和在DFS中的不一样,这个数组表示,从(status,i)到根节点的距离,就是(status,i) 所处的层数,那么当status==目标状态,则返回dis[status][i]就是本题的答案, 这个代码看起来就很简洁明了。

代码如下:

class Solution 
{
public:
    int shortestPathLength(vector<vector<int>>& graph) 
    {
        vector<vector<int>> dis(1<<graph.size(),vector<int> (graph.size(),INT_MAX));
        queue<pair<int,int>> q;
        for(int i=0;i<graph.size();i++)
        {
            dis[1<<i][i]=0;
            q.push(make_pair(1<<i,i));
        }
        while(!q.empty())
        {
            auto p=q.front();
            q.pop();
            if(p.first+1==(1<<graph.size()))
                return dis[p.first][p.second];
            int x=p.first, y=p.second;
            for(auto num:graph[y])
            {
                int status=x | (1<<num);
                if(dis[x][y]+1<dis[status][num])
                {
                    dis[status][num]=dis[x][y]+1;
                    q.push(make_pair(status,num));
                }
            }
        }
        return 0;
    }
};

当然,上面的代码用了一个二维数组记录状态,其实开的空间稍微大了些,有些空间并未直接用上,那么我们只需要用一个set来保存已经访问的状态即可,因为是一层一层地遍历,访问过的状态就不再访问了,最后当我们找到目标状态的时候,返回目标状态所在的层数就可以了。下面是按照这个思路写的BFS代码:

class Solution 
{
public:
    int shortestPathLength(vector<vector<int>>& graph) 
    {
        set<pair<int,int>> s;
        int n=graph.size();
        queue<pair<int,int>> q;
        for(int i=0;i<n;i++)
        {
            pair<int,int> p;
            p.first=1<<i;
            p.second=i;
            s.insert(p);
            q.push(p);
        }
        int layer=0; //当前层数
        while(!q.empty())
        {
            int m=q.size();
            for(int i=0;i<m;i++)
            {
                auto p=q.front();
                q.pop();
                int x=p.first;
                int y=p.second;
                if(x+1==(1<<n)) return layer;
                for(auto num:graph[y])
                {
                    int temp=x|(1<<num);
                    if(!s.count({temp,num}))
                    {
                        s.insert({temp,num});
                        q.push({temp,num});
                    }
                } 
            }
            layer++; //层数加1
        }
        return layer;
    }
};
通过这道题,充分复习了DFS+memo和BFS的解题思路以及具体细节,首先要对状态转移进行合理有效的建模,然后画出状态转移的树状图,再按照DFS和BFS的标准步骤进行代码编写。只有思路清晰才能在最短的时间内写出最正确的代码来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值