332.重新安排行程
332. 重新安排行程 - 力扣(LeetCode)
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] 输出:["JFK","MUC","LHR","SFO","SJC"]
示例 2:
输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]] 输出:["JFK","ATL","JFK","SFO","ATL","SFO"] 解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。
提示:
1 <= tickets.length <= 300
tickets[i].length == 2
fromi.length == 3
toi.length == 3
fromi
和toi
由大写英文字母组成fromi != toi
思路
这道题是一个求解欧拉通路的题目。关于欧拉回路和欧拉通路,定义如下:
1、通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路;
2、通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路;
3、具有欧拉回路的无向图称为欧拉图;
4、具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。
为了使答案的字典序最小,我们每次都应该选择当前节点所连接的字典序最小的那个节点并将其入栈,最后栈中就会保存字典序最小的遍历顺序。在Java中,我们可以使用优先队列PriorityQueue来实现字典序的自动排序,这样每次都可以O(1)地找到最小字典序的节点,并O(logm)地删除
这种算法叫做Hierholzer算法,用于在连通图中寻找欧拉路径,其流程如下:
1、从起点出发,进行dfs
2、每次沿着某条便从某个顶点移动到另外一个顶点的时候,都需要删除这条边。
3、如果没有可移动的路径,则将所在节点加入栈中,并返回。
代码如下:
class Solution {
Map<String,PriorityQueue<String>> map=new HashMap<String,PriorityQueue<String>>();
List<String> itinerary=new LinkedList<String>();
public List<String> findItinerary(List<List<String>> tickets) {
for(List<String> ticket : tickets){
String src=ticket.get(0),dst=ticket.get(1);
if(!map.containsKey(src)){
map.put(src,new PriorityQueue<String>());
}
map.get(src).offer(dst);
}
dfs("JFK");
Collections.reverse(itinerary);
return itinerary;
}
public void dfs(String curr){
while(map.containsKey(curr)&&map.get(curr).size()>0){
String tmp =map.get(curr).poll();
dfs(tmp);
}
itinerary.add(curr);
}
}
有的时候会出现特殊情况,当访问到字典序排较前节点时,此时发现该节点没有邻居,即走入了死胡同,正常情况下,此时会停止搜索并返回路径,但是这样就会漏掉剩余未访问的边,我们无法判断当前节点的哪一个分支是死胡同,所以这个问题看起来很难解决。但是在一个欧拉通路中,只有那个入度与出度差为1的节点会导致死胡同,而该节点必然是最后一个遍历到的节点,所以该算法改变了入栈的规则,当遍历完一个节点所连的所有节点之后,我们才将该节点入栈。
while(map.containsKey(curr)&&map.get(curr).size()>0){
String tmp =map.get(curr).poll();
dfs(tmp);
}
最后的栈中会存储路径的逆序,使用Collections来进行反转即可。
Collections.reverse(itinerary);
再拓展一下,PriorityQueue可以实现不同的优先级排序,底层使用的是堆排序。
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(10,new Comparator<Integer>(){
public int compare(Integer a, Integer b){
return a-b; //if a>b 则交换,so这是递增序列
}
});
// 创建使用自定义比较器的 PriorityQueue
PriorityQueue<Integer> pq3 = new PriorityQueue<>(Comparator.reverseOrder());