847. 访问所有节点的最短路径

Difficulty
Hard
Tags
BFS
URL
https://leetcode.cn/problems/shortest-path-visiting-all-nodes/
Star
存在一个由 n 个节点组成的无向连通图,图中的节点按从 0n - 1 编号。
给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。
返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。
示例 1:
notion imagenotion image
输入:graph = [[1,2,3],[0],[0],[0]] 输出:4 解释:一种可能的路径为 [1,0,2,0,3]
示例 2:
notion imagenotion image
输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]] 输出:4 解释:一种可能的路径为 [0,1,4,2,3]
提示:
  • n == graph.length
  • 1 <= n <= 12
  • 0 <= graph[i].length < n
  • graph[i] 不包含 i
  • 如果 graph[a] 包含 b ,那么 graph[b] 也包含 a
  • 输入的图总是连通图
通过次数22,481提交次数33,093

法1 BFS + 状态压缩

关键点: 本题hard在何处?在于对状态的表示 在一般的BFS中,我们一般只记录自己当前在哪个点,以及过去访问过哪些点,且这些点是无重复的。 在本题,以示例为例,在第一次访问0和第二次访问0的时候,其实完全是两种状态。第一次访问0时,已访问过1;第二次访问0时,已访问过0,1,2。
notion imagenotion image
在搜索时,我们希望能带着这个 visited 进行搜索,但是如果带着一个数组或者集合,显然非常不方便,拷贝起来非常慢,一般这种时候我们会考虑回溯。但本题需要找到一个 最短的路径,因此回溯就显得不合时宜了(回溯适合以下两种情况:找到所有方案数量、找到所有方案)。
观察到n是一个比较小的数,因此可以用状态压缩,即用一个数字来表示状态。即用二进制中的某一位来表示 当前位置是否被访问过,例如 0110表示位置 1 2 被访问过。
注意,本题的 visited 存储的是 (point, state) 的集合,而非常规BFS中的 point 的集合。
思路
题解
class Solution: def shortestPathLength(self, graph: List[List[int]]) -> int: n = len(graph) target = (1 << n) - 1 # 遍历所有的起点 res = float("inf") for s in range(0, n): res = min(res, self.helper(s, target, graph)) return res def helper(self, start: int, target: int, graph:List[List[int]]): """从 start 出发 需要的长度 """ queue = deque() visited = set() queue.append((start, 1<<start)) # 当前位置,状态 visited.add((start, 1<<start)) step = 0 while queue: step += 1 size = len(queue) # print(queue) while size: cur_loc, cur_state = queue.popleft() size -= 1 for nxt_loc in graph[cur_loc]: nxt_state = cur_state | 1 << nxt_loc if nxt_state == target: return step if (nxt_loc, nxt_state) in visited: continue visited.add((nxt_loc, nxt_state)) queue.append((nxt_loc, nxt_state)) return 0