⭐算法入门⭐《动态规划 - 状态压缩DP》困难01 —— LeetCode 847. 访问所有节点的最短路径

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了⭐算法入门⭐《动态规划 - 状态压缩DP》困难01 —— LeetCode 847. 访问所有节点的最短路径相关的知识,希望对你有一定的参考价值。

🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《数据结构入门》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

究极算法奥义!深度学习!
🟣《深度学习100例》🟣

一、题目

1、题目描述

  给出 g r a p h graph graph 为有 n ( n ≤ 12 ) n(n \\le 12) n(n12) 个节点(编号为 0, 1, 2, …, n − 1 n-1 n1)的无向连通图。 g r a p h . l e n g t h = n graph.length = n graph.length=n,且只有节点 i i i j j j 连通时, j j j 不等于 i i i 时且在列表 g r a p h [ i ] graph[i] graph[i] 中恰好出现一次。返回能够访问所有节点的最短路径的长度。可以在任一节点 开始结束,也可以多次重访节点,并且可以重用边
  样例输入: [ [ 1 , 2 , 3 ] , [ 0 ] , [ 0 ] , [ 0 ] ] [[1,2,3],[0],[0],[0]] [[1,2,3],[0],[0],[0]]
  样例输出: 4

2、基础框架

  • c++ 版本给出的基础框架代码如下:
class Solution {
public:
    int shortestPathLength(vector<vector<int>>& graph) {
    }
};
  • graph代表的是一个单向的邻接表;

3、原题链接

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

二、解题报告

1、思路分析

  • 这是一个可重复访问的 旅行商问题。我们可以设计状态如下:
  • f ( s t , e n , s t a t e ) f(st, en, state) f(st,en,state) 代表从 s t st st e n en en,且 经过的节点 的状态组合为 s t a t e state state 的最短路径。
  • 状态组合的含义是:经过二进制来压缩得到的一个数字,比如 经过的节点 为 0、1、4,则 s t a t e state state 的二进制表示为: ( 10011 ) 2 (10011)_2 (10011)2
  • 经过的节点 对应到状态 s t a t e state state 二进制表示的位为 1,其余位为 0。
  • 于是,我们明确以下几个定义:初始状态、最终状态、非法状态、中间状态。

1)初始状态

  • 初始状态 一定是我们确定了某个起点,想象一下,假设起点在 i i i,那么在一开始的时候,必然 s t a t e = 2 i state = 2^i state=2i。于是,我们可以认为起点在 i i i,终点在 i i i,状态为 2 i 2^i 2i 的最短路径为 0。也就是初始状态表示如下:
  • f ( i , i , 2 i ) = 0   i ∈ [ 0 , n ) f(i, i, 2^i) = 0 \\\\ \\ \\\\ i \\in [0, n) f(i,i,2i)=0 i[0,n)

2)最终状态

  • 由于这个问题,没有告诉我们 起点终点,所以 起点终点 是不确定的,我们需要通过枚举来得出,所以最终状态 起点 i i i终点 j j j,状态为 2 n − 1 2^n-1 2n1(代表所有点都经过,二进制的每一位都为 1)。最终状态表示为:
  • f ( i , j , 2 n − 1 )   i ∈ [ 0 , n ) , j ∈ [ 0 , n ) f(i, j, 2^n-1) \\\\ \\ \\\\ i \\in [0, n), j \\in [0, n) f(i,j,2n1) i[0,n),j[0,n)

3)非法状态

  • 非法状态 就是 不可能从初始状态 通过 状态转移 到达的状态。
  • 我们设想一下,如果 f ( i , j , s t a t e ) f(i, j, state) f(i,j,state) s t a t e state state 的二进制的第 i i i 位为 0,或者第 j j j 位 为 0,则这个状态必然是非法的,它代表了两个矛盾的对立面。
  • 我们把非法状态下的最短路径定义成 i n f = 10000000 inf = 10000000 inf=10000000 即可。
  • f ( i , j , s t a t e ) = i n f f(i, j, state) = inf f(i,j,state)=inf
  • 其中 (state & (1<<i)) == 0或者(state & (1<<j)) == 0代表state的二进制表示的第 i i i j j j 位没有 1。

4)中间状态

  • 除了以上三种状态以外的状态,都成为中间状态。

  • 那么,我们可以通过 记忆化搜索,枚举所有的 f ( i , j , 2 n − 1 ) f(i, j, 2^n - 1) f(i,j,2n1) 的,求出一个最小值就是我们的答案了。
  • 有关记忆化搜索的内容,可以参考:夜深人静写算法(二十六)- 记忆化搜索

2、时间复杂度

  • 状态数: O ( n 2 2 n ) O(n^22^n) O(n22n)
  • 状态转移: O ( n ) O(n) O(n)
  • 时间复杂度: O ( n 3 2 n ) O(n^32^n) O(n32n)

3、代码详解

const int maxn = 12;
const int inf  = 100000000;

class Solution {
    int n;
    int mat[maxn][maxn];                                   // (1)
    int f[maxn][maxn][1<<maxn];                            // (2)
public:
    void fillGraphMatrix(vector<vector<int>>& graph) {     // (3)
        memset(mat, 0, sizeof(mat));
        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < graph[i].size(); ++j) {
                mat[i][ graph[i][j] ] = 1;
            }
        }
    }

    int dfs(int st, int en, int state) {                   // (4)
        if(  !( (1<<st) & state ) ) {                      // (5)
            return inf;
        }
        if(  !( (1<<en) & state ) ) {                      // (6)
            return inf;
        }
        if(st == en) {
            if(state == (1<<st)) {
                return 0;                                  // (7)
            }
        }

        int &ret = f[st][en][state];
        if(ret != -1) {
            return ret;                                    // (8)
        }
        ret = inf;
        for(int i = 0; i < n; ++i) {                       // (9)
            // (st -> ... -> i)  U  (i -> en) 
            if(!mat[i][en]) {                              // (10)
                continue;
            }
            int a = dfs(st, i, state);                     // (11)
            int b = dfs(st, i, state ^ (1<<en));           // (12)
            ret = min( ret, min(a, b) + 1 );               // (13)
        }
        return ret;
    }

    int shortestPathLength(vector<vector<int>>& graph) {
        n = graph.size();
        fillGraphMatrix(graph);
        int ret = 100000000;
        memset(f, -1, sizeof(f));

        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < n; ++j) {
                int &ans = f[i][j][(1<<n) - 1];
                ans = dfs(i, j, (1<<n) - 1);              // (14)
                ret = min(ans, ret);
            }
        }
        return ret;
    }
};