重新安排行程

标签: 深度优先搜索 欧拉回路

难度: Hard

给你一份航线列表 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

Submission

运行时间: 21 ms

内存: 16.5 MB

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        import collections 
        self.ans=None
        maps=collections.defaultdict(list)
        for a,b in tickets:
            if a not in maps:
                maps[a]=[b]
            else:
                maps[a].append(b)
        for k in maps:
            heapq.heapify(maps[k])
        
        def dfs(cur):
            while maps[cur]:
                tmp=heapq.heappop(maps[cur])
                dfs(tmp)
            stack.append(cur)
        stack=[]
        dfs("JFK")
        return stack[::-1]
        


Explain

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)

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        import collections
        import heapq  # Used for maintaining a min-heap
        self.ans = None  # Not used, can be removed
        maps = collections.defaultdict(list)  # Graph representation
        for a, b in tickets:
            maps[a].append(b)  # Fill adjacency list
        for k in maps:
            heapq.heapify(maps[k])  # Heapify each destination list
        
        def dfs(cur):
            while maps[cur]:
                tmp = heapq.heappop(maps[cur])  # Always choose lexicographically smallest airport
                dfs(tmp)  # Recursively travel to the next airport
            stack.append(cur)  # Add airport to stack after visiting all possible next airports
        
        stack = []
        dfs('JFK')  # Start DFS from 'JFK'
        return stack[::-1]  # Return reversed stack as the final itinerary

Explore

在构建图时使用最小堆而不是直接排序列表的主要原因是效率。使用最小堆可以更高效地维护和更新目的地顺序。在深度优先搜索过程中,可能需要频繁地插入和删除元素。如果使用排序列表,每次插入或删除都可能需要O(n)的时间来维护排序,其中n是列表长度。而使用最小堆,则可以在O(log n)的时间内进行插入和删除操作,这样可以更加高效地找到字典序最小的下一个目的地。

这个算法通过使用回溯机制来确保使用所有的机票并找到有效路径。在DFS过程中,一旦发现当前路径不能使用所有机票或无法继续前进时,算法会退回到上一个节点,并尝试其他可能的路径。这种方式确保了每一张机票都被考虑,并且在探索所有可能的路径后找到一个有效的解决方案。此外,通过使用最小堆确保每次选择的都是字典序最小的目的地,有助于按照字典序生成最终的行程列表。

这种倒序的原因是因为在深度优先搜索中,节点(机场)是在完成探索所有从该节点出发的可能路径后才被添加到栈中的。这意味着最后访问的节点(通常是行程的终点)最先被放入栈中,而起始点则最后被放入。因此,当所有节点都被处理完毕时,栈中的元素顺序实际上是行程的逆序。将栈反转后,可以得到正确的行程顺序。

在这种情况下,说明从该机场已无其他可行的出发航线,因此这个机场应当成为行程的一个终点。在DFS过程中,一旦到达这样的机场,就会将其添加到栈中,并回溯到上一个机场继续探索其他可能的航线。这样,算法确保了即使某些机场在之前的路径中已使用完所有航线,也能正确地回溯并完成整个行程的构建。