An undirected, connected graph of N nodes (labeled `0, 1, 2, ..., N-1`) is given as `graph`.

graph.length = N, and j != i is in the list graph[i] exactly once, if and only if nodes i and j are connected.

Return the length of the shortest path that visits every node. You may start and stop at any node, you may revisit nodes multiple times, and you may reuse edges.

Example 1:

Input: [[1,2,3],[0],[0],[0]]
Output: 4
Explanation: One possible path is [1,0,2,0,3]

Example 2:

Input: [[1],[0,2,4],[1,3,4],[2],[1,2]]
Output: 4
Explanation: One possible path is [0,1,4,2,3]

Note:

  1. 1 <= graph.length <= 12
  2. 0 <= graph[i].length < graph.length

这道题说是给了一个无向图,里面有N个结点,让我们找到一条可以经过所有结点的路径,该路径的起点和终点任意选,只要能经过所有结点即可,这里的每个结点和边都可以重复经过,问这样一条路径的最短长度是多少,注意这里的长度不是路径结点的个数,而是结点中的边的个数。先来想一下,假如这些结点是一字排开的,则最短经过所有结点的路径就类似于遍历链表一样的,但假如这些结点是围绕着一个中心结点的话,比如本题中的例子1,则中心结点会被经过多次,感觉不太好整啊。博主之前说过求极值的问题有两大神器,动态规划 Dynamic Programming 和广度优先搜索 Breadth First Search,这里碰巧两种方法都能解。先来看看 BFS 的解法吧,这种解法最经典的应用是在迷宫问题中,找到起点和终点之间的最短距离,假如把每个位置都看作一个状态的话,BFS 可以推广到更一般的情况。在迷宫中每一步可能会有上下左右四个方向可以选,每走一步其实可以看作是一个状态转移到另一个状态,当到达终点状态时,就可以得到最少步数了。这里也是类似,首先要定义起始状态和终止状态,本题关心的是要经过所有的结点,终止状态就是经过所有结点,起始状态就是只经过了起始结点,那该如何编码这些状态呢?最直接的方法就是把经过的结点放到数组或者 HashSet 中,但是这样的话每次检验是否到达终止状态的时候,都要检测数组或者 HashSet 中是否包含了所有的结点,这会很费时,因为在 BFS 的每一层遍历中都会检测是否到达终止状态。还有就是每个状态是由当前遍历的结点跟当前结点标号组成的,假如把遍历过的结点放到数组或集合中,再跟当前结点标号一起组成 pair 对儿放入队列 queue 中,将会占用大量的空间。

基于以上分析,貌似必须想一种更好的方法来编码遍历过的结点,这里用到了位操作 Bit Manipulation 的技巧,对没使用过的童鞋来说会比较 tricky。对于任意结点i,假如遍历过了,则将其对应位上变为1,即 ‘或’ 上 1<<i,这样每个结点都可以被分别编码进对应位,则遍历过n个结点的十进制数就是 2^n-1 了,只要某个状态的十进制数等于 2^n-1,则表示到达了终止状态。另外,由于最短路径的起点不定,那么这里的 BFS 的起点就应该是所有的结点,将每个结点都当作起始结点,并将结点编号编码到十进制数中,和当前位置一起组成 pair 对儿放进队列中。将n个起点都放入队列之后,就可以开始遍历了,它们都属于同一层,这里进行的是 BFS 的层序遍历的形式。对于每个取出的元素,首先判断取出的状态的 pair 对儿的第一个编码十进制数是否等于最终结果值 target,是的话直接返回结果 res。然后再根据第二个位置值去 graph 数组中查找所有与其相邻的结点,对于每个相邻的结点 next,由于在之前的基础上又加上了结点 next,这也要编码进去,所以要 ‘或’ 上 1<<next,然后在 visted 集合中查找该新状态是否存在,不存在的话加入 visited 集合,并把编码成的十进制数 path 和当前结点编号 next 组成新的 pair 对儿加入队列进行下次遍历。每层遍历结束后记得结果 res 要自增1,while 循环退出后返回 -1,其实根本不会返回 -1,因为题目中是无向连通图,一定会有经过所有结点的路径存在,这里只是怕不写返回值会报错而已,参见代码如下:

解法一:

class Solution {
public:
int shortestPathLength(vector<vector<int>>& graph) {
int n = graph.size(), target = 0, res = 0;
unordered_set<string> visited;
queue<pair<int, int>> q;
for (int i = 0; i < n; ++i) {
int mask = (1 << i);
target |= mask;
visited.insert(to_string(mask) + "-" + to_string(i));
q.push({mask, i});
}
while (!q.empty()) {
for (int i = q.size(); i > 0; --i) {
auto cur = q.front(); q.pop();
if (cur.first == target) return res;
for (int next : graph[cur.second]) {
int path = cur.first | (1 << next);
string str = to_string(path) + "-" + to_string(next);
if (visited.count(str)) continue;
visited.insert(str);
q.push({path, next});
}
}
++res;
}
return -1;
}
};

再来看一种 DP 的解法,这种解法的核心思想跟上面的 BFS 方法很类似,我们用一个二维的 dp 数组,其中 dp[i][j] 表示的某个状态时经过的结点编码成的十进制数i,且当前位置为结点j时的路径长度。这样的话只要当i到达 2^n-1 的时候,此时所有的 dp[2^n-1][j] 中的最小值即为所求,这种定义状态的方式可以说和上面的解法完全一样。就像上面解法中将n个结点都当作起始点,并将其状态存入队列中的操作一样,这里要将所有的 dp[1
解法二:

class Solution {
public:
int shortestPathLength(vector<vector<int>>& graph) {
int n = graph.size(), res = n * n;
vector<vector<int>> dp(1 << n, vector<int>(n, n * n));
for (int i = 0; i < n; ++i) dp[1 << i][i] = 0;
for (int cur = 0; cur < (1 << n); ++cur) {
bool repeat = true;
while (repeat) {
repeat = false;
for (int i = 0; i < n; ++i) {
int dist = dp[cur][i];
for (int next : graph[i]) {
int path = cur | (1 << next);
if (dist + 1 < dp[path][next]) {
dp[path][next] = dist + 1;
if (path == cur) repeat = true;
}
}
}
}
}
for (int num : dp.back()) {
res = min(res, num);
}
return res;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/847

类似题目:

Shortest Path to Get All Keys

参考资料:

https://leetcode.com/problems/shortest-path-visiting-all-nodes/

https://leetcode.com/problems/shortest-path-visiting-all-nodes/discuss/135712/Java-BFS

https://leetcode.com/problems/shortest-path-visiting-all-nodes/discuss/152679/Short-Java-Solution-BFS-with-a-Set

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 847. Shortest Path Visiting All Nodes 访问所有结点的最短路径的更多相关文章

  1. leetcode 847. Shortest Path Visiting All Nodes 无向连通图遍历最短路径

    设计最短路径 用bfs 天然带最短路径 每一个状态是 当前的阶段 和已经访问过的节点 下面是正确但是超时的代码 class Solution: def shortestPathLength(self, ...

  2. LeetCode 847. Shortest Path Visiting All Nodes

    题目链接:https://leetcode.com/problems/shortest-path-visiting-all-nodes/ 题意:已知一条无向图,问经过所有点的最短路径是多长,边权都为1 ...

  3. [Leetcode]847. Shortest Path Visiting All Nodes(BFS|DP)

    题解 题意 给出一个无向图,求遍历所有点的最小花费 分析 1.BFS,设置dis[status][k]表示遍历的点数状态为status,当前遍历到k的最小花费,一次BFS即可 2.使用DP 代码 // ...

  4. 【LeetCode】847. Shortest Path Visiting All Nodes 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/shortest ...

  5. 847. Shortest Path Visiting All Nodes

    An undirected, connected graph of N nodes (labeled 0, 1, 2, ..., N-1) is given as graph. graph.lengt ...

  6. [Swift]LeetCode847. 访问所有节点的最短路径 | Shortest Path Visiting All Nodes

    An undirected, connected graph of N nodes (labeled 0, 1, 2, ..., N-1) is given as graph. graph.lengt ...

  7. 最短路径遍历所有的节点 Shortest Path Visiting All Nodes

    2018-10-06 22:04:38 问题描述: 问题求解: 本题要求是求遍历所有节点的最短路径,由于本题中是没有要求一个节点只能访问一次的,也就是说可以访问一个节点多次,但是如果表征两次节点状态呢 ...

  8. AOJ GRL_1_C: All Pairs Shortest Path (Floyd-Warshall算法求任意两点间的最短路径)(Bellman-Ford算法判断负圈)

    题目链接:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_C All Pairs Shortest Path Input ...

  9. LeetCode 1091. Shortest Path in Binary Matrix

    原题链接在这里:https://leetcode.com/problems/shortest-path-in-binary-matrix/ 题目: In an N by N square grid, ...

随机推荐

  1. Linux高性能服务器编程,书中的 shell 命令

    记录<Linux高性能服务器编程>书里面讲解到的若干 shell 命令 arp 命令查看ARP高速缓存: [root@VM_0_10_centos heliang]# arp -a ? ( ...

  2. LeetCode 225:用队列实现栈 Implement Stack using Queues

    题目: 使用队列实现栈的下列操作: push(x) -- 元素 x 入栈 pop() -- 移除栈顶元素 top() -- 获取栈顶元素 empty() -- 返回栈是否为空 Implement th ...

  3. SQL Server 2014:为什么数据库里的表提示“单元格是只读的”,不能修改?该如何处理?

    出现以上这种情况,首先看一下这个字段的属性“标识规范”是不是选了“是”,自增属性下是不能修改的,属于只读.

  4. 分布式中session共享的解决方案:spring-session

    Session是客户端与服务器通讯会话跟踪技术,是服务器与客户端保持整个通讯的会话基本信息.客户端在第一次访问服务器的时候,服务端会响应一个sessionId并且将它存入到本地的Cookie中,在之后 ...

  5. Autoware 培训笔记 No. 2——基于点云的定位

    1. 前言 构建出地图后,应该测试点云地图定位效果,这里用到ndt的scan_matching方法,这是一种scan-to-map方法.这里用的是我们自己采集的数据进行仿真. 本章内容有和No. 1重 ...

  6. WebApi生成文档

    本文包括两个部分: webapi中使用swagger 修改webapi的路由和默认参数 WebApi中使用swagger 项目打开之后,选择 引用,右键,管理NuGet程序包 浏览,搜索swagger ...

  7. 命令 docker rm | docker rmi | docker prune 的差异

    区别: docker rm : 删除一个或多个 容器 docker rmi : 删除一个或多个 镜像 docker prune : 用来删除不再使用的 docker 对象 一.docker rm 命令 ...

  8. CTF挑战赛丨网络内生安全试验场第一季答题赛火热开启

    前期回顾:挑战世界级“人机大战”,更有万元奖金等你来拿 网络内生安全试验场自上线以来,受到了业内的极大重视与关注. 自9月2日报名通道开启后,报名量更是持续高升,上百名精英白帽踊跃报名. 至此,网络内 ...

  9. Arduino+esp8266-01+舵机 制作基于局域网的遥控门禁

    这个最终的效果呢,就是可以通过手机连接上esp8266创建的wifi,然后连接其创建的服务器,发送特定指令就可实现遥控开门 (做工比较粗糙还请不要见笑...) 一.原理 其实这个一看就会明白,非常简单 ...

  10. 我的第一次diy装机记录——小白的装机篇

    接上一篇<我的第一次diy装机记录——小白的配置篇> 处理器 AMD Ryzen 5 2600X 六核主板 微星 B450M MORTAR (MS-7B89) ( AMD PCI 标准主机 ...