leetcode
leetcode 301 ~ 350
重新安排行程

重新安排行程

难度:

标签:

题目描述

给你一份航线列表 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
  • fromitoi 由大写英文字母组成
  • fromi != toi

代码结果

运行时间: 21 ms, 内存: 16.5 MB


/*
 * 思路:
 * 1. 将所有航班票按照出发地为键,目的地为值放入一个Map中,值为一个优先队列,方便按字典序排序。
 * 2. 使用DFS从JFK开始构建行程,每次选择字典序最小的航班。
 * 3. 使用LinkedList将行程存储,因为DFS后行程是逆序的,使用LinkedList的addFirst方法可以避免最后反转列表。
 */
import java.util.*;
import java.util.stream.Collectors;
 
public class SolutionStream {
    public List<String> findItinerary(List<List<String>> tickets) {
        Map<String, PriorityQueue<String>> flights = tickets.stream()
            .collect(Collectors.groupingBy(
                ticket -> ticket.get(0), 
                Collectors.mapping(
                    ticket -> ticket.get(1), 
                    Collectors.toCollection(PriorityQueue::new)
                )
            ));
        LinkedList<String> itinerary = new LinkedList<>();
        dfs("JFK", flights, itinerary);
        return itinerary;
    }
 
    private void dfs(String airport, Map<String, PriorityQueue<String>> flights, LinkedList<String> itinerary) {
        PriorityQueue<String> destinations = flights.get(airport);
        while (destinations != null && !destinations.isEmpty()) {
            dfs(destinations.poll(), flights, itinerary);
        }
        itinerary.addFirst(airport);
    }
}
 

解释

方法:

The solution uses a depth-first search (DFS) combined with a min-heap for lexical order and a stack to build the itinerary. Initially, it constructs a graph with directed edges from each ticket's departure to its destination, using a defaultdict of lists. Each list is then converted into a min-heap to ensure that the next destination selected is the lexicographically smallest available. Starting from 'JFK', the DFS explores as far as possible along each branch, pushing the airports to the stack once all possible paths from that airport are exhausted. After the DFS completes, the stack holds the itinerary in reverse order, which is then reversed to produce the final itinerary order. This approach inherently handles the backtracking by exploring all possible routes using each ticket exactly once.

时间复杂度:

O(E log m)

空间复杂度:

O(V + E)

代码细节讲解

🦆
为什么在构建图时选择使用最小堆(min-heap)而不是直接排序列表?
在构建图时使用最小堆而不是直接排序列表的主要原因是效率。使用最小堆可以更高效地维护和更新目的地顺序。在深度优先搜索过程中,可能需要频繁地插入和删除元素。如果使用排序列表,每次插入或删除都可能需要O(n)的时间来维护排序,其中n是列表长度。而使用最小堆,则可以在O(log n)的时间内进行插入和删除操作,这样可以更加高效地找到字典序最小的下一个目的地。
🦆
在深度优先搜索(DFS)中,如果某条路径被堵塞,算法是如何确保使用所有的机票并找到有效路径的?
这个算法通过使用回溯机制来确保使用所有的机票并找到有效路径。在DFS过程中,一旦发现当前路径不能使用所有机票或无法继续前进时,算法会退回到上一个节点,并尝试其他可能的路径。这种方式确保了每一张机票都被考虑,并且在探索所有可能的路径后找到一个有效的解决方案。此外,通过使用最小堆确保每次选择的都是字典序最小的目的地,有助于按照字典序生成最终的行程列表。
🦆
您提到在深度优先搜索结束后,栈中的行程是倒序的。这种倒序的原因是什么?
这种倒序的原因是因为在深度优先搜索中,节点(机场)是在完成探索所有从该节点出发的可能路径后才被添加到栈中的。这意味着最后访问的节点(通常是行程的终点)最先被放入栈中,而起始点则最后被放入。因此,当所有节点都被处理完毕时,栈中的元素顺序实际上是行程的逆序。将栈反转后,可以得到正确的行程顺序。
🦆
如果某个机场的所有出发航线都已经在之前的路径中使用过,但该机场又被重新访问,这种情况下该如何处理?
在这种情况下,说明从该机场已无其他可行的出发航线,因此这个机场应当成为行程的一个终点。在DFS过程中,一旦到达这样的机场,就会将其添加到栈中,并回溯到上一个机场继续探索其他可能的航线。这样,算法确保了即使某些机场在之前的路径中已使用完所有航线,也能正确地回溯并完成整个行程的构建。

相关问题