题目地址:
https://www.lintcode.com/problem/reconstruct-itinerary/description
给定一个有向图,每个顶点是个字符串(其实是机场代码),要求返回一个字典序最小的以"JFK"
开始的欧拉路径。题目保证该欧拉路径存在。
找欧拉路径有一个经典的Hierholzer算法。这个算法的基本思路是,从某个顶点出发,先顺着边走,每走一步就删掉走过的边,直到走到某个顶点无边可走时,开始回溯,回溯之前插入顶点到列表中(这个列表就存了欧拉路径,操作有点像DFS求拓扑排序。这里之所以要回溯的时候再加入列表,原因是只有回溯的时候走的点才能构成一条路径,如果每一层递归直接就将点加入列表,那么回溯到上一个分叉口的时候,再加入点,这个时候路径是断开的,并不是一条连通的路径),直到回溯到某个顶点有另一条边可以走的时候,再去走那条边,重复上述过程。这个过程的本质就是,DFS的时候走到尽头了如果还有边没走,则会通过回溯,将没走的圈加进DFS路径中(由欧拉路径的性质,没走的边一定能形成圈)。具体代码有两种实现方式,即递归实现和栈实现。
本题还有一个额外要求,即要返回字典序最小的欧拉路径。这一点可以在DFS选边的时候,每次选字典序最小的点去走即可。求完欧拉路径再反个序,得到的列表就是每次选边都尽量选字典序小的顶点的边走的欧拉路径。
法1:DFS。代码如下:
import java.util.*;
public class Solution {
/**
* @param tickets: a list of a list of String
* @return: a list of String
*/
public List<String> findItinerary(List<List<String>> tickets) {
// Write your code here
List<String> res = new ArrayList<>();
Map<String, PriorityQueue<String>> graph = buildGraph(tickets);
dfs("JFK", graph, res);
// 此时res存的是字典序最小的欧拉路径的逆序,还需要反个序
Collections.reverse(res);
return res;
}
private void dfs(String cur, Map<String, PriorityQueue<String>> graph, List<String> res) {
if (graph.containsKey(cur)) {
PriorityQueue<String> minHeap = graph.get(cur);
// 每次都挑一个最小字典序的边去走
while (!minHeap.isEmpty()) {
dfs(minHeap.poll(), graph, res);
}
}
// 回溯的时候再将点加入列表
res.add(cur);
}
private Map<String, PriorityQueue<String>> buildGraph(List<List<String>> tickets) {
Map<String, PriorityQueue<String>> graph = new HashMap<>();
for (List<String> ticket : tickets) {
String from = ticket.get(0), to = ticket.get(1);
// 每个点的邻接点按字典序加入最小堆中,这样每次选边的时候都会选到字典序最小的点
graph.putIfAbsent(from, new PriorityQueue<>());
graph.get(from).offer(to);
}
return graph;
}
}
时间复杂度 O ( V log E ) O(V\log E) O(VlogE),空间 O ( V + E ) O(V+E) O(V+E)。
法2:栈。代码如下:
import java.util.*;
public class Solution {
/**
* @param tickets: a list of a list of String
* @return: a list of String
*/
public List<String> findItinerary(List<List<String>> tickets) {
// Write your code here
List<String> res = new ArrayList<>();
Map<String, PriorityQueue<String>> graph = buildGraph(tickets);
Deque<String> stack = new LinkedList<>();
String cur = "JFK";
stack.push(cur);
while (!stack.isEmpty()) {
if (graph.containsKey(cur) && !graph.get(cur).isEmpty()) {
stack.push(cur);
cur = graph.get(cur).poll();
} else {
res.add(cur);
cur = stack.pop();
}
}
Collections.reverse(res);
return res;
}
private Map<String, PriorityQueue<String>> buildGraph(List<List<String>> tickets) {
Map<String, PriorityQueue<String>> graph = new HashMap<>();
for (List<String> ticket : tickets) {
String from = ticket.get(0), to = ticket.get(1);
graph.putIfAbsent(from, new PriorityQueue<>());
graph.get(from).offer(to);
}
return graph;
}
}
时空复杂度一样。