题目描述
给出 graph
为有 N 个节点(编号为 0, 1, 2, ..., N-1
)的无向连通图。
graph.length = N
,且只有节点 i
和 j
连通时,j != i
在列表 graph[i]
中恰好出现一次。
返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。
示例 1:
输入:[[1,2,3],[0],[0],[0]]
输出:4
解释:一个可能的路径为 [1,0,2,0,3]
示例 2:
输入:[[1],[0,2,4],[1,3,4],[2],[1,2]]
输出:4
解释:一个可能的路径为 [0,1,4,2,3]
提示:
1 <= graph.length <= 12
0 <= graph[i].length < graph.length
反思错误:
- 第一遍用的dfs,每个点进行dfs,超过2*n就停止,或者全部点都被访问截止,,,毫无疑问最后一个超时
- 之前没接触过状压DP,没想过用二进制位运算来代替状态,,厉害啊
解题思路:
在了解状压DP之前,首先要了解二进制位运算一些知识【这里有详细的状压DP及位运算;(我写的不会那么详细)】,
& 与运算:6(110)& 4(100) = 4(100),,,不解释了
| 或运算:6(110) | 4(100) = 6(110)
^ 异或运算:6(110) ^ 4(100) = 2(010)
<< 左移运算:1<<3 = 1 * 2³ = 8(1000) 将1(0001)向左移动3位 ==》 8(1000) ;简单理解就是左值乘以2的右值次幂。
>> 右移运算:相对应的就是向右移,,也就是除2
看这道题,拿示例一来说:
图是这个样子的,,,我们首先要确定,每个点是否被访问过应该用二进制来表示,也就是
1(0001)代表0节点被访问,1、2、3没被访问。。
6(0110)代表1、2节点被访问,0、3没有………………{因为二进制数字0110从右往左数第零三位置为0,一二位置为1}
知道了每个节点的状态之后,创建dp[节点个数][所有状态都没被访问+1],,按这题来说
int[][] dp = new int[4][16] ------------------因为我们最大状态(1111),申请的空间都是从零开始的,你不会不知道吧。我们需要申请大小为16(10000),所以是(10000),我也说不清了,,,,就类似我们数字有0、1、2、3,但是申请需要 [4] 。
然后需要两个Queue队列存储每个节点编号 id 和它对应的状态 state
把初始每个点放入队列,让队列中每次都走一步,这样第一个出现走完所有点,也就是状态为(1111)的dp值就是最小路。
我觉得看代码更清晰。
Java代码:
class Solution {
public int shortestPathLength(int[][] graph) {
int n = graph.length;//number of points in graph
int statelen = 1<<n;//state:已访问点的状态,四个点就是2^4(二进制10000)
int[][] dp = new int[n][statelen];//第n点时 ,其他点访问状态state 时的 最小步数
Queue<Integer> queueid = new LinkedList<>();
Queue<Integer> queuestate = new LinkedList<>();
//初始化
int end=0;
for(int i=0;i<n;++i){
queueid.offer(i);
int state = 1<<i;
queuestate.offer(state);//1表示访问过了
end |=state;//表示结束情况是每个点都被访问了
}
while(!queueid.isEmpty()){
int id = queueid.remove();
int state = queuestate.remove();
if(state==end)return dp[id][state];
for(int nextid:graph[id]){
int nextstate = state|1<<nextid;
if(dp[nextid][nextstate]==0) {
queueid.add(nextid);
queuestate.add(nextstate);
dp[nextid][nextstate] = dp[id][state]+1;
}
}
}
return -1;
}
}