Leetcode0847. Shortest path to visit all nodes (difficult, BFS, state compression)

content

1. Problem description

2. Method 1: Single-source breadth-first search

2.1 Ideas

2.2 Code Implementation

3. Method 2: State Compression + Breadth First Search

3.1 Ideas

3.2 Code

4. Method 3: Preprocessing the shortest path between point pairs + state compression dynamic programming


1. Problem description

There is an  n undirected connected graph consisting of nodes, and the nodes in the graph are numbered from  0 to  n - 1 .

gives you an array  graph representing this graph. where, graph[i] is a list consisting of all nodes that  i are directly connected to the node. Returns the length of the shortest path that can visit all nodes. You can start and stop at any node, revisit nodes multiple times, and reuse edges.

Example 1:

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

Example 2:

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

hint:

  • n == graph.length
  • 1 <= n <= 12
  • 0 <= graph[i].length < n
  • graph[i] does not contain i
  • If  graph[a] included  b , then  graph[b] also included a
  • The input graph is always a connected graph

Source: LeetCode
Link: https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes The
copyright belongs to LeetCode.com. For commercial reprints, please contact the official authorization, and for non-commercial reprints, please indicate the source.

2. Method 1: Single-source breadth-first search

2.1 Ideas

        Shortest path problem, breadth-first search.

        However, in this problem, the nodes can be visited repeatedly, and the edges can also be repeated, the purpose is to traverse all the nodes. Therefore, during the traversal process, when looking for adjacent nodes, it is no longer subject to the constraint that the nodes that have been visited in the basic graph traversal will not be visited.

        Of course, the visited nodes still need to record traces. The search stops once all nodes have been visited at least once, and the resulting number of layers is the minimum path length required for the desired traversal.

        Equivalent to the graph to be searched is a hypergraph generated from the input graph.

        graph is the adjacency list representation of the input graph.

        Further, the results from different points may be different, so the traversal starts with all the various nodes as the starting point to search. and take the smallest value.

2.2 Code Implementation

from typing import List
from collections import deque
class Solution:
    def shortestPathLength(self, graph: List[List[int]]) -> int:
        def bfs(start):
            q       = deque([[start]])
            # visited = set([0])
            while True:
                path = q.popleft()
                # print("path = {0}", path)
                if len(set(path))==len(graph):
                    return len(path)-1
                for neighbor in graph[path[-1]]:
                    q.append(path + [neighbor])
            
        minpath = bfs(0)
        for k in range(1,len(graph)):
            pathlen = bfs(k)
            # print(k,pathlen)
            minpath = pathlen if pathlen < minpath else minpath
        return minpath

        Miserable timeout. . .

        The reason is that the above scheme is a direct expansion and does not exclude possible repeated visits. The size of the tree grows rapidly when there are 12 nodes.

3. Method 2: State Compression + Breadth First Search

        For the solution of this question, please refer to the official solution of learning.

3.1 Ideas

        Since the problem requires us to find "the length of the shortest path that visits all nodes", and the length of each edge in the graph is 1, we can consider using the breadth-first search method to find the shortest path.

        In a regular breadth-first search, we store the node's number in a queue. For this problem, the premise of the shortest path is that "all nodes have been visited", so in addition to recording the number of the node, we also need to record the path taken to reach the current node. Therefore, we use a triple (u,mask,dist) to represent each element in the queue, where:

        u represents the node number currently visited;

        mask is a binary number of length n, indicating whether each node passes through. If the i-th bit of mask is 1, it means that node i has passed, otherwise, it means that node i has not passed;

        dist represents the length of the path traversed until the current node (that is, the layer number of the breadth-first search)

        This way, we can solve this problem by performing a breadth-first search using that triple. Initially, we put all (i, 2^i, 0) into the queue, indicating that we can start from any node. During the search, if the mask in the current triple contains n 1s (ie mask = 2^n - 1, then we can return dist as the answer.

        There are a few key points in the above ideas, which are explained further:

  • (1) The above solution is to treat the problem as a multi-source BFS. Of course, you can also do single-source BFS processing for each node, and then do a for loop. The latter is exactly the practice of the above method 1, but this "single-source BFS+for loop" method will cause the repetition between different unit BFSs to not be optimized (there will be a large number of repeated visits), which may be the above Root Cause of Method 1
  • (2) Use mask to represent path history, which is the so-called state compression
  • (3) To record the previous access state, you only need to pay attention to the last node and which nodes have been visited in the history of the path, and do not need to care about the order in which these nodes are accessed. This is the key to improving efficiency. This actually requires a proof or at least a qualitative explanation.

3.2 Code

class Solution:
    def shortestPathLength(self, graph: List[List[int]]) -> int:
        n = len(graph)
        q = deque((i, 1 << i, 0) for i in range(n))
        seen = {(i, 1 << i) for i in range(n)}
        ans = 0
        
        while q:
            u, mask, dist = q.popleft()
            if mask == (1 << n) - 1:
                ans = dist
                break
            # 搜索相邻的节点
            for v in graph[u]:
                # 将 mask 的第 v 位置为 1
                mask_v = mask | (1 << v)
                if (v, mask_v) not in seen:
                    q.append((v, mask_v, dist + 1))
                    seen.add((v, mask_v))
        
        return ans

        Execution time: 164 ms, beat 95.43% of users in all Python3 commits

        Memory consumption: 19.9 MB, beating 35.67% of users across all Python3 commits

4. Method 3: Preprocessing the shortest path between point pairs + state compression dynamic programming

        This is another solution given by the official solution.

        To be added.

        Back to the main directory: Leetcode daily problem-solving notes (dynamic update...)

Guess you like

Origin blog.csdn.net/chenxy_bwave/article/details/124224735