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

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

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

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

数据结构难?不存在的!
🌳《画解数据结构》🌳

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

一、题目

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;
    }
};
  • ( 1 ) (1) (1) mat[][]为领接矩阵,mat[i][j] == 1代表 i i i j j j 之间有一条有向边;

  • ( 2 ) (2) (2) f[i][j][state]表示从 i i i j j j,且所有经过的节点的二进制组合状态为 s t a t e state state 的最短路径;

  • ( 3 ) (3) (3) fillGraphMatrix用于将 邻接表 转换成 邻接矩阵

  • ( 4 ) (4) (4) int dfs(int st, int en, int state)代表记忆化搜索,返回值即f[i][j][state]

  • ( 5 ) (5) (5) 上文提到的其中一种 非法状态

  • ( 6 ) (6) (6) 上文提到的令一种 非法状态

  • ( 7 ) (7) (7) 上文提到的 初始状态

  • ( 8 ) (8) (8) 记忆化搜索的部分,用 − 1 -1 1 代表状态还没求出来;求过的状态直接返回;

  • ( 9 ) (9) (9) s t → e n st \to en sten 拆分成 ( s t → . . . → i ) (st \to ... \to i) (st...i) U ( i → e n ) (i \to en) (ien);也就是引入中间节点 i i i,拆分成子问题求解;

  • ( 10 ) (10) (10) 如果 i → e n i \to en ien 是不连通的,自然子问题拆分失败,则进行下一个节点枚举;

  • ( 11 ) (11) (11) dfs(st, i, state)代表从 s t → i st \to i sti 并且已经经过 e n en en 的最短路径和;

  • ( 12 ) (12) (12) dfs(st, i, state ^ (1<<en))代表从 s t → i st \to i sti 并且尚未经过 e n en en 的最短路径和;

  • ( 13 ) (13) (13) ( 11 ) (11) (11) ( 12 ) (12) (12) 两者中的最小值,更新最优解;

  • ( 14 ) (14) (14) 枚举所有可能的最终状态进行求解。


三、本题小知识

  这题可以优化成 O ( n 2 2 n ) O(n^22^n) O(n22n),可以尝试一下。


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值