LeetCode 847 - Shortest Path Visiting All Nodes


https://leetcode.com/problems/shortest-path-visiting-all-nodes/
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

    https://blog.csdn.net/fuxuemingzhu/article/details/82939203
    话说看到这个题的第一感觉就是BFS,因为我们要找到遍历所有节点的最少步数,这个正是BFS擅长的。唯一不同的就是这个题允许从多个顶点出发,也就是说没有了固定的起点。那么需要对BFS稍微改变一点,即在初始化的时候,把所有顶点都放进队列之中,这样,每次把队列的元素pop出来一遍之后就是新的一轮循环,也就可以认为所有的节点都是同时向前迈进了一步。

    这个题使用了一个的技巧,位运算。一般的BFS过程都是只保存访问过的节点即可,因为每个节点只可以使用一次,但是这个题的节点可以访问多次,那么就是说必须维护一个实时的访问了哪些节点的状态。按道理说,如果不使用位运算而是使用字典等方式保存访问过了的状态也可以,但是,看了给出的图的顶点个数只有12个,哪怕一个int都会有32个bit够用,所以可以直接使用和图中顶点数相等的位数来保存这个状态是否访问过。这个状态怎么理解?从每个顶点出发到达,所有访问过的节点是状态。也就是说这个状态是全局唯一的,每个顶点都有2 * N个状态表示它访问的其他节点。有2 ^ N个bit,每个位都代表对应的节点是否访问过。最终的状态是(1 << N) - 1,即全是1,表示所有节点都访问了。

    这个visited是个二维数组,保存的是每个节点的所有状态,对于该题目的BFS,有可能有N * 2^N个状态,使用visited保存每个节点已经访问的状态,对应状态位置是0/1。

    时间复杂度是O(N * (2^N)),空间复杂度是O(N * 2^N)。

    https://tinacristal.github.io/2018/12/07/shor/
    用二进制的数来表示当前访问节点的状态
    4个节点 如1101表示 0,2,3号节点已访问过
    最后的终止状态是1111
    或者用Hashtable 也可以
    为了遍历所有的节点 一个节点可以被重复访问 所以不能用 vis[cur] 跳过访问过的节点
    但是不跳过访问过的状态 可能会陷入 0->1->0的死循环
    所以建立二维数组vis 记录当前访问过的节点和状态
    不会重复在这个节点的访问状态
    即 在1号节点已经访问过0号,1号房间
    pair<int,int> {1,0011}
    即在搜索1号节点的邻居时不会再访问0号节点 因为此时状态不会更新
    还是 0011
    https://github.com/cherryljr/LeetCode/blob/master/Shortest%20Path%20Visiting%20All%20Nodes.java
     * 题目要求走遍所有点的最短路径(最少步数)。而该图是一个 权值为1的无向图。
     * 并且数据规模为 node <= 12, 所以首先可以考虑使用 BFS 来做。
     *
     * 该题与平时遇到的 BFS 不同点在于,在同一条路径(同一轮遍历)中,一个点是可以被重复遍历的。
     * 而我们平时都是会记录一个 visited 数组来避免遍历重复的点,或者是限制加入队列的条件
     * 来减小问题的规模。否则就会出现死循环。而这也是本题的难点所在。
     *
     * 对此我们仍然可以通过记录 visited 状态来解决。
     * 只不过这里需要记录的状态为:当前的位置 以及 对应的遍历过的节点状态
     * 当我们从一个节点出发,遍历后没有新的节点增加的话,那就说明我们走的路是无用的。
     * 同时因为节点个数最多只有 12 个,所以我们可以通过 二进制 来表示状态从而达到优化的效果。
     *
     * 注:这里使用了 进队列 时进行判断的做法,代码上看上去可能没那么好看(for循环遍历邻居时)
     * 但是在时间上可以优化不少。
     * 关于这点的分析可以详细参考:
     *  https://github.com/cherryljr/LintCode/blob/master/Matrix%20Water%20Injection.java
    

        public int shortestPathLength(int[][] graph) {
            int n = graph.length;
            // 采用 入队列时判断 的BFS做法,因此需要处理一下起始情况。
            if (n <= 1) {
                return 0;
            }
    
            Queue<int[]> queue = new LinkedList<>();
            boolean[][] visited = new boolean[n][1 << n];
            // 可以选择任意点作为起点,因此开始时需要将所有的节点都 add 到队列中
            for (int i = 0; i < n; i++) {
                queue.offer(new int[]{i, 1 << i});
                visited[i][1 << i] = true;
            }
            // 采用二进制记录状态信息
            int target = (1 << n) - 1;
    
            int step = 0;
            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    int[] curr = queue.poll();
                    int pos = curr[0], state = curr[1];
    
                    for (int neigh : graph[pos]) {
                        int nextState = state | (1 << neigh);
                        if (nextState == target) {
                            return step + 1;
                        }
                        if (visited[neigh][nextState]) {
                            continue;
                        }
                        visited[neigh][nextState] = true;
                        queue.offer(new int[]{neigh, state | (1 << neigh)});
                    }
                }
                step++;
            }
    
            return -1;
        }
    https://leetcode.com/problems/shortest-path-visiting-all-nodes/discuss/135686/Java-DP-Solution
    https://leetcode.com/problems/shortest-path-visiting-all-nodes/discuss/135809/Fast-BFS-Solution-(46ms)-Clear-Detailed-Explanation-Included
    we could get rid of the cost attribute just by looping the queue by levels. It will cause less confusion. See
    https://leetcode.com/articles/shortest-path-visiting-all-nodes/
    Approach #1: Breadth First Search [Accepted]
    A path 'state' can be represented as the subset of nodes visited, plus the current 'head' node. Then, the problem reduces to a shortest path problem among these states, which can be solved with a breadth-first search.
    Let's call the set of nodes visited by a path so far the cover, and the current node as the head. We'll store the cover states using set bits: k is in the cover if the kth bit of cover is 1.
    For states state = (cover, head), we can perform a breadth-first search on these states. The neighbors are (cover | (1 << child), child) for each child in graph[head].
    If at any point we find a state with all set bits in it's cover, because it is a breadth-first search, we know this must represent the shortest path length.
    • Time Complexity: O(2^N * N).
    • Space Complexity: O(2^N * N).
      public int shortestPathLength(int[][] graph) {
        int N = graph.length;
        Queue<State> queue = new LinkedList<>();
        int[][] dist = new int[1 << N][N];
        for (int[] row : dist)
          Arrays.fill(row, N * N);

        for (int x = 0; x < N; ++x) {
          queue.offer(new State(1 << x, x));
          dist[1 << x][x] = 0;
        }

        while (!queue.isEmpty()) {
          State node = queue.poll();
          int d = dist[node.cover][node.head];
          if (node.cover == (1 << N) - 1)
            return d;

          for (int child : graph[node.head]) {
            int cover2 = node.cover | (1 << child);
            if (d + 1 < dist[cover2][child]) {
              dist[cover2][child] = d + 1;
              queue.offer(new State(cover2, child));

            }
          }
        }

        throw null;
      }

    class State {
      int cover, head;

      State(int c, int h) {
        cover = c;
        head = h;
      }

    }


    Approach #2: Dynamic Programming [Accepted]
    http://bookshadow.com/weblog/2018/06/03/leetcode-shortest-path-visiting-all-nodes/
    Floyd + 动态规划(Dynamic Programming)
    时间复杂度 O(2^n * n^2)
    利用Floyd求出每对顶点i, j之间的最短距离,记为dp[i][j],代价为O(N^3)
    
    利用status[s][i]记录:状态为s,当前所在节点为i时的最小路径长度
    
    状态s是二进制,表示各节点是否被访问过,1表示已访问,0表示未访问
    
    状态转移方程:
    
    status[ns][j] = min(status[ns][j], status[s][i] + dp[i][j])
    
    其中ns表示从状态s的i点出发到达j点时的新状态


    A path 'state' can be represented as the subset of nodes visited, plus the current 'head' node. As in Approach #1, we have a recurrence in states: answer(cover, head) is min(1 + answer(cover | (1<<child), child) for child in graph[head]). Because these states form a DAG (a directed graph with no cycles), we can do dynamic programming.
    Algorithm
    Let's call the set of nodes visited by a path so far the cover, and the current node as the head. We'll store dist[cover][head] as the length of the shortest path with that cover and head. We'll store the coverstates using set bits, and maintain the loop invariant (on cover), that dist[k][...] is correct for k < cover.
    For every state (cover, head), the possible next (neighbor) nodes in the path are found in graph[head]. The new cover2 is the old cover plus next.
    For each of these, we perform a "relaxation step" (for those familiar with the Bellman-Ford algorithm), where if the new candidate distance for dist[cover2][next] is larger than dist[cover][head] + 1, we'll update it to dist[cover][head] + 1.
    Care must be taken to perform the relaxation step multiple times on the same cover if cover = cover2. This is because a minimum state for dist[cover][head] might only be achieved through multiple steps through some path.
    Finally, it should be noted that we are using implicitly the fact that when iterating cover = 0 .. (1<<N) - 1, that each new cover cover2 = cover | 1 << x is such that cover2 >= cover. This implies a topological ordering, which means that the recurrence on these states form a DAG.
    • Time Complexity: O(2^N * N).
    • Space Complexity: O(2^N * N).
      public int shortestPathLength(int[][] graph) {
        int N = graph.length;
        int dist[][] = new int[1 << N][N];
        for (int[] row : dist)
          Arrays.fill(row, N * N);
        for (int x = 0; x < N; ++x)
          dist[1 << x][x] = 0;

        for (int cover = 0; cover < 1 << N; ++cover) {
          boolean repeat = true;
          while (repeat) {
            repeat = false;
            for (int head = 0; head < N; ++head) {
              int d = dist[cover][head];
              for (int next : graph[head]) {
                int cover2 = cover | (1 << next);
                if (d + 1 < dist[cover2][next]) {
                  dist[cover2][next] = d + 1;
                  if (cover == cover2)
                    repeat = true;
                }
              }
            }
          }
        }

        int ans = N * N;
        for (int cand : dist[(1 << N) - 1])
          ans = Math.min(cand, ans);
        return ans;

      }



    Labels

    LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

    Popular Posts