🙉饭不食,水不饮,题必须刷🙉
C语言免费动漫教程,和我一起打卡! 🌞《光天化日学C语言》🌞
LeetCode 太难?先看简单题! 🧡《C语言入门100例》🧡
数据结构难?不存在的! 🌳《画解数据结构》🌳
LeetCode 太简单?算法学起来! 🌌《夜深人静写算法》🌌
一、题目
1、题目描述
给出 g r a p h graph graph 为有 n ( n ≤ 12 ) n(n \le 12) n(n≤12) 个节点(编号为 0, 1, 2, …, n − 1 n-1 n−1)的无向连通图。 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、原题链接
二、解题报告
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 2n−1(代表所有点都经过,二进制的每一位都为 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,2n−1) 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,2n−1) 的,求出一个最小值就是我们的答案了。
- 有关记忆化搜索的内容,可以参考:夜深人静写算法(二十六)- 记忆化搜索。
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 st→en 拆分成 ( s t → . . . → i ) (st \to ... \to i) (st→...→i) U ( i → e n ) (i \to en) (i→en);也就是引入中间节点 i i i,拆分成子问题求解;
-
( 10 ) (10) (10) 如果 i → e n i \to en i→en 是不连通的,自然子问题拆分失败,则进行下一个节点枚举;
-
( 11 ) (11) (11)
dfs(st, i, state)
代表从 s t → i st \to i st→i 并且已经经过 e n en en 的最短路径和; -
( 12 ) (12) (12)
dfs(st, i, state ^ (1<<en))
代表从 s t → i st \to i st→i 并且尚未经过 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),可以尝试一下。