--- comments: true difficulty: 困难 edit_url: https://github.com/doocs/leetcode/edit/main/solution/0800-0899/0847.Shortest%20Path%20Visiting%20All%20Nodes/README.md tags: - 位运算 - 广度优先搜索 - 图 - 动态规划 - 状态压缩 --- # [847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes) [English Version](/solution/0800-0899/0847.Shortest%20Path%20Visiting%20All%20Nodes/README_EN.md) ## 题目描述

存在一个由 n 个节点组成的无向连通图,图中的节点按从 0n - 1 编号。

给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。

返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。

 

示例 1:

输入:graph = [[1,2,3],[0],[0],[0]]
输出:4
解释:一种可能的路径为 [1,0,2,0,3]

示例 2:

输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
输出:4
解释:一种可能的路径为 [0,1,4,2,3]

 

提示:

## 解法 ### 方法一:状态压缩 + BFS 我们注意到 $n$ 的范围不超过 $12$,因此,我们可以用一个 $12$ 位的二进制数来表示每个节点的访问情况,其中第 $i$ 位为 $1$ 表示第 $i$ 个节点已经被访问过,为 $0$ 表示该节点还没有被访问过。 我们初始化队列 $q$,其中每个元素是一个二元素 $(i, st)$,表示当前位于节点 $i$,且已经遍历过的节点的集合为 $st$。初始时,队列中只有 $n$ 个元素,即 $(i, 2^i)$,表示可以从任一节点出发开始遍历。另外,我们用一个哈希表或数组 $vis$ 记录每个状态是否已经被搜索过,防止无效的重复搜索。 在 BFS 的过程中,我们每次取出队首元素 $(i, st)$,如果当前 $st$ 包含 $n$ 个 $1$,那么我们就找到了一条从起点出发的遍历路径,返回当前的步数即可。否则我们枚举当前节点 $i$ 的所有连边 $(i, j)$,如果 $(j, st \lor 2^j)$ 没有被搜索过,那么就将 $(j, st \lor 2^j)$ 加入队列 $q$ 中,并且用 $vis$ 记录它已经被搜索过。循环此过程,直到找到一条路径。 时间复杂度 $(n^2 \times 2^n)$,空间复杂度 $O(n \times 2^n)$。其中 $n$ 是图中的节点数。 #### Python3 ```python class Solution: def shortestPathLength(self, graph: List[List[int]]) -> int: n = len(graph) q = deque() vis = set() for i in range(n): q.append((i, 1 << i)) vis.add((i, 1 << i)) ans = 0 while 1: for _ in range(len(q)): i, st = q.popleft() if st == (1 << n) - 1: return ans for j in graph[i]: nst = st | 1 << j if (j, nst) not in vis: vis.add((j, nst)) q.append((j, nst)) ans += 1 ``` #### Java ```java class Solution { public int shortestPathLength(int[][] graph) { int n = graph.length; Deque q = new ArrayDeque<>(); boolean[][] vis = new boolean[n][1 << n]; for (int i = 0; i < n; ++i) { q.offer(new int[] {i, 1 << i}); vis[i][1 << i] = true; } for (int ans = 0;; ++ans) { for (int k = q.size(); k > 0; --k) { var p = q.poll(); int i = p[0], st = p[1]; if (st == (1 << n) - 1) { return ans; } for (int j : graph[i]) { int nst = st | 1 << j; if (!vis[j][nst]) { vis[j][nst] = true; q.offer(new int[] {j, nst}); } } } } } } ``` #### C++ ```cpp class Solution { public: int shortestPathLength(vector>& graph) { int n = graph.size(); queue> q; bool vis[n][1 << n]; memset(vis, false, sizeof(vis)); for (int i = 0; i < n; ++i) { q.emplace(i, 1 << i); vis[i][1 << i] = true; } for (int ans = 0;; ++ans) { for (int k = q.size(); k; --k) { auto [i, st] = q.front(); q.pop(); if (st == (1 << n) - 1) { return ans; } for (int j : graph[i]) { int nst = st | 1 << j; if (!vis[j][nst]) { vis[j][nst] = true; q.emplace(j, nst); } } } } } }; ``` #### Go ```go func shortestPathLength(graph [][]int) int { n := len(graph) q := [][2]int{} vis := make([][]bool, n) for i := range vis { vis[i] = make([]bool, 1< 0; k-- { p := q[0] q = q[1:] i, st := p[0], p[1] if st == (1< new Array(1 << n).fill(false)); for (let i = 0; i < n; ++i) { q.push([i, 1 << i]); vis[i][1 << i] = true; } for (let ans = 0; ; ++ans) { for (let k = q.length; k; --k) { const [i, st] = q.shift()!; if (st === (1 << n) - 1) { return ans; } for (const j of graph[i]) { const nst = st | (1 << j); if (!vis[j][nst]) { vis[j][nst] = true; q.push([j, nst]); } } } } } ``` #### Rust ```rust use std::collections::VecDeque; impl Solution { #[allow(dead_code)] pub fn shortest_path_length(graph: Vec>) -> i32 { let n = graph.len(); let mut vis = vec![vec![false; 1 << n]; n]; let mut q = VecDeque::new(); // Initialize the queue for i in 0..n { q.push_back(((i, 1 << i), 0)); vis[i][1 << i] = true; } // Begin BFS while !q.is_empty() { let ((i, st), count) = q.pop_front().unwrap(); if st == (1 << n) - 1 { return count; } // If the path has not been visited for j in &graph[i] { let nst = st | (1 << *j); if !vis[*j as usize][nst] { q.push_back(((*j as usize, nst), count + 1)); vis[*j as usize][nst] = true; } } } -1 } } ``` ### 方法二:BFS(A\* 算法) 因为每条边权值一样,所以用 BFS 就能得出最短路径,过程中可以用**状态压缩**记录节点的访问情况。另外,同一个节点 u 以及对应的节点访问情况需要保证只被搜索过一次,因此可以用 `vis(u, state)` 表示是否已经被搜索过,防止无效的重复搜索。 本题也属于 BFS 最小步数模型,可以使用 A\* 算法优化搜索。 A\* 算法主要思想如下: 1. 将 BFS 队列转换为优先队列(小根堆); 1. 队列中的每个元素为 `(dist[state] + f(state), state)`,`dist[state]` 表示从起点到当前 state 的距离,`f(state)` 表示从当前 state 到终点的估计距离,这两个距离之和作为堆排序的依据; 1. 当终点第一次出队时,说明找到了从起点到终点的最短路径,直接返回对应的 step; 1. `f(state)` 是估价函数,并且估价函数要满足 `f(state) <= g(state)`,其中 `g(state)` 表示 state 到终点的真实距离; 1. A\* 算法只能保证终点第一次出队时,即找到了一条从起点到终点的最小路径,不能保证其他点出队时也是从起点到当前点的最短路径。 #### Python3 ```python class Solution: def shortestPathLength(self, graph: List[List[int]]) -> int: n = len(graph) def f(state): return sum(((state >> i) & 1) == 0 for i in range(n)) q = [] dist = [[inf] * (1 << n) for _ in range(n)] for i in range(n): heappush(q, (f(1 << i), i, 1 << i)) dist[i][1 << i] = 0 while q: _, u, state = heappop(q) if state == (1 << n) - 1: return dist[u][state] for v in graph[u]: nxt = state | (1 << v) if dist[v][nxt] > dist[u][state] + 1: dist[v][nxt] = dist[u][state] + 1 heappush(q, (dist[v][nxt] + f(nxt), v, nxt)) return 0 ``` #### Java ```java class Solution { private int n; public int shortestPathLength(int[][] graph) { n = graph.length; int[][] dist = new int[n][1 << n]; for (int i = 0; i < n; ++i) { Arrays.fill(dist[i], Integer.MAX_VALUE); } PriorityQueue q = new PriorityQueue<>(Comparator.comparingInt(a -> a[0])); for (int i = 0; i < n; ++i) { q.offer(new int[] {f(1 << i), i, 1 << i}); dist[i][1 << i] = 0; } while (!q.isEmpty()) { int[] p = q.poll(); int u = p[1], state = p[2]; if (state == (1 << n) - 1) { return dist[u][state]; } for (int v : graph[u]) { int nxt = state | (1 << v); if (dist[v][nxt] > dist[u][state] + 1) { dist[v][nxt] = dist[u][state] + 1; q.offer(new int[] {dist[v][nxt] + f(nxt), v, nxt}); } } } return 0; } private int f(int state) { int ans = 0; for (int i = 0; i < n; ++i) { if (((state >> i) & 1) == 0) { ++ans; } } return ans; } } ``` #### C++ ```cpp class Solution { public: int n; int shortestPathLength(vector>& graph) { n = graph.size(); priority_queue, vector>, greater>> q; vector> dist(n, vector(1 << n, INT_MAX)); for (int i = 0; i < n; ++i) { q.push({f(1 << i), i, 1 << i}); dist[i][1 << i] = 0; } while (!q.empty()) { auto [_, u, state] = q.top(); q.pop(); if (state == (1 << n) - 1) return dist[u][state]; for (int v : graph[u]) { int nxt = state | (1 << v); if (dist[v][nxt] > dist[u][state] + 1) { dist[v][nxt] = dist[u][state] + 1; q.push({dist[v][nxt] + f(nxt), v, nxt}); } } } return 0; } int f(int state) { int ans = 0; for (int i = 0; i < n; ++i) if (((state >> i) & 1) == 0) ++ans; return ans; } }; ```