输入:有N个节点的无向图,每个节点被标注为0,1,…N-1。graph[i][j]表示从节点i到节点j有一条边。
输出:每个节点都访问一次,至少需要几步。
规则:可以重复访问一个节点。
分析:这道题目看了2个星期。其实这两周也是因为加班,没有再看新的题目。在地铁上没事,就把官方的解法方式拿出来看看。发现另有感悟。体会到了书读百遍其义自见。
我们需要返回沿着边走完N个节点需要几步。因为我们可以从任意一个节点开始走。所以我们可以先问从节点0开始,走几步能访问了所有的节点。如果我们知道这个答案,那么我们分别计算从0,1,2…N-1开始,需要几步。取最小值就是答案。
输入示例:[[1,2,3],[0],[0],[0]]
先计算从一个节点开始。假设从节点0开始。可能的路径:节点0->节点1->?,之后需要再经过节点0,才有出路。我认为这道题目难的地方在于,之前为了防止重复访问,一个元素被访问之后做个标记,就可以。但是这里不可以。节点0->节点1->节点0->?如果此时再访问节点1,那就进入死循环了。既要一个节点访问多次,又要防止进入死循环。那么这里去重的不是节点,而是访问过的节点路径。
状态1:节点0->节点1,访问了节点0和1,当前节点是1。
状态2:节点0->节点1->节点0,也是访问了节点0和1,当前节点是0。
上面的两种状态,虽然都只有两个节点被访问,但是当前节点不同,那么能选择的路径就可以不同。所以状态1和状态2是不同的状态,不能被放弃。
状态3:节点0->节点1->节点0->节点1,也是访问了节点0和1,当前节点是1。描述完之后就发现状态3和状态1是一样的。而状态3的步数更多。所以状态3是属于重复状态,需要放弃。
之后的可能的状态是:
状态4:节点0->节点1->节点0->节点2
状态5:节点0->节点1->节点0->节点2->节点0
状态6:节点0->节点1->节点0->节点2->节点0->节点3
也就是说需要5步可以遍历完所有节点。
我们可以使用DFS或者BFS遍历边。编码的重点是解决怎么记录状态。我们需要记录当前已经访问了哪些节点,当前节点是哪个。前者使用bit数组记录。1=访问了节点0;3=访问了节点0和1;2=访问了节点1…
2
N
−
1
=
2^N-1=
2N−1=访问了所有节点。
节点标签 | N-1 | N-2 | … | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
代表值 | 2 N − 1 2^{N-1} 2N−1 | 2 N − 2 2^{N-2} 2N−2 | … | 2 3 2^3 23 | 2 2 2^2 22 | 2 1 2^1 21 | 2 0 2^0 20 |
public class ShortestPathVisitingAllNodesV2 {
public static void main(String[] args){
int[][] graph = new int[4][];
graph[0] = new int[]{1,2,3};
graph[1] = new int[]{0};
graph[2] = new int[]{0};
graph[3] = new int[]{0};
int r = new ShortestPathVisitingAllNodesV2().shortestPathLengthDFS(graph);
System.out.println(r);
}
public int shortestPathLengthBFS(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);
int startNode = 1;
dist[1<<startNode][startNode] = 0;
queue.offer(new State(1<<startNode,startNode));
//BFS遍历
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
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 newCover = node.cover | 1 << child;
if(dist[newCover][child]>d+1){
dist[newCover][child] = d +1;
queue.offer(new State(newCover,child));
}
}
}
}
throw null;
}
private int minDis = Integer.MAX_VALUE;
public int shortestPathLengthDFS(int[][] graph) {
int N = graph.length;
int[][] dist = new int[1<<N][N];
for (int[] row: dist) Arrays.fill(row, N*N);
int startNode = 0;
dist[1<<startNode][startNode] = 0;
dfs(new State(1<<startNode,startNode),graph,N,dist);
return minDis;
}
private void dfs(State state,int[][] graph,int N,int[][] dist) {
if(state.cover == (1<<N)-1){
minDis = Math.min(minDis,dist[state.cover][state.head]);
}else{
for(int child : graph[state.head]){
int newCover = state.cover | 1 << child;
if(dist[newCover][child]>dist[state.cover][state.head]+1){
dist[newCover][child] = dist[state.cover][state.head]+1;
dfs(new State(newCover,child),graph,N,dist);
}
}
}
}
class State {
int cover, head;
State(int c, int h) {
cover = c;
head = h;
}
}
}
再进一步计算从各个节点开始。修改代码
int startNode = 1;
dist[1<<startNode][startNode] = 0;
queue.offer(new State(1<<startNode,startNode));
修改为:
for(int startNode=0;startNode<N;startNode++){
dist[1<<startNode][startNode] = 0;
queue.offer(new State(1<<startNode,startNode));
}