在问 · 算法题解

332. 重新安排行程

困难图论Hierholzer(显式栈)力扣原题 ↗

题目描述

输入一组机票 tickets,每张机票 tickets[i] = [from, to] 表示一段从机场 from 飞往机场 to 的航程。机场用三个大写字母表示。

所有机票属于同一位旅客。他从 "JFK" 出发,要把每张机票都用掉,而且每张只能用一次。请按飞行顺序排出他经过的机场。

合法行程可能不止一种。此时把行程看成字符串列表,按字典序比较,返回最小的那一种。例如 ["JFK","LGA"] 比 ["JFK","LGB"] 小。

返回行程中依次经过的机场列表,第一个元素是 "JFK"。题目保证至少有一种合法行程。

示例 1
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

解释:机票只能连成一条路线: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
  • from.length == 3
  • to.length == 3
  • from 和 to 只含大写英文字母
  • from != to

解题思路:Hierholzer(显式栈)

每张机票是一条有向边,要找一条用完所有边的欧拉路径,用 Hierholzer 算法。把每个机场的出边按字典序排好,每次走最小的那条。用栈模拟 DFS:栈顶还有出边就沿着走;没有出边了(走进死胡同),就把它弹出加入 route。最后 route 反过来就是行程。

为什么正确

一个机场只有在它的出边全部用完后才会被加入 route,所以 route 是行程的逆序:先入 route 的是最后到达的地方。

复杂度分析

时间 O(E log E)(排序),空间 O(E)。

易错点

贪心地按字典序直接走可能走进死胡同(如 JFK→KUL 无法返回)。Hierholzer 的“走不动才加入 route、最后反转”恰好把死胡同放到行程末尾。另外出边要按降序存放,才能用 pop() 每次取最小的。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        graph = collections.defaultdict(list)
        for a, b in sorted(tickets, reverse=True):
            graph[a].append(b)
        route = []
        stack = ['JFK']
        while stack:
            while graph[stack[-1]]:
                stack.append(graph[stack[-1]].pop())
            route.append(stack.pop())
        return route[::-1]

载入编辑器,自己跑一遍 →

其他解法

两种写法都是 Hierholzer 求欧拉路径:递归版用系统调用栈,主解法用显式栈避免递归过深。

解法 2:Hierholzer(递归) 时间 O(E log E),空间 O(E)

把每个机场的目的地按字典序倒序存放,这样从末尾弹出的就是字典序最小的目的地。从 JFK 开始递归:只要当前机场还有未用的机票,就弹出一张并递归进入目的地。当前机场的机票用完后把它加入路线。最后反转路线。

是官方题解的写法,递归代码更短,“无路可走时才入栈”的性质更直观。机票很多时递归很深,显式栈版本更稳妥。

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        graph = collections.defaultdict(list)
        for a, b in sorted(tickets, reverse=True):
            graph[a].append(b)
        route = []

        def dfs(u):
            while graph[u]:
                dfs(graph[u].pop())
            route.append(u)

        dfs('JFK')
        return route[::-1]

图论 专题的其他题目

查看 图论 全部题目 →