题目来源:LeetCode332:重新安排行程
问题抽象: 给定机票列表 tickets(tickets[i] = [from_i, to_i] 表示从 from_i 机场飞往 to_i 机场的行程),要求 重建行程路线 使其满足以下核心需求:
-
行程规则:
- 起点固定 :路线必须从
"JFK"机场开始; - 全覆盖 :所有机票 必须被使用且仅使用一次(每条边遍历一次);
- 字典序最小 :若存在多条可行路线,返回 字典序最小 的行程(如
["JFK","ABC"]优先于["JFK","ACB"])。
- 起点固定 :路线必须从
-
输入约束:
- 机票数量
∈ [1, 300]; - 机场代码为 3个大写字母 (如
"JFK","SFO"); - 保证至少存在一条可行路线(无需处理无解场景)。
- 机票数量
-
图结构特性:
- 机票表示 有向边 (
from_i → to_i); - 路线需形成 欧拉路径(所有边遍历一次);
- 可能存在 多重边(同一航线多张机票)。
- 机票表示 有向边 (
-
输出要求:
- 返回机场代码列表(按行程顺序排列);
- 示例:
tickets=[["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
→["JFK","MUC","LHR","SFO","SJC"];tickets=[["JFK","KUL"],["JFK","NRT"],["NRT","JFK"]]
→["JFK","NRT","JFK","KUL"](字典序最小)。
-
关键挑战:
- 字典序优化 :每个机场的下一站需按字典序排序(优先选
"ABC"而非"ACB"); - 环检测 :需处理 局部环 (如
JFK→NRT→JFK→KUL); - 多重边消耗:同航线多张机票需独立消耗。
- 字典序优化 :每个机场的下一站需按字典序排序(优先选
输入 :二维字符串数组 tickets(如 [["JFK","SFO"]])
输出:字符串列表(完整行程路线)。
解题思路
题目本质是寻找有向图的欧拉路径(Eulerian Path),即一条访问图中每条边恰好一次的路径。要求从 JFK 出发,且返回字典序最小的行程。Hierholzer 算法可高效求解欧拉路径,结合贪心策略(每次选择字典序最小的邻居)确保结果字典序最小。
关键步骤:
-
建图:
- 使用
HashMap存储每个出发机场对应的到达机场列表。 - 为每个出发机场的到达机场列表使用最小堆(
PriorityQueue),确保每次取字典序最小的邻居。
- 使用
-
Hierholzer 算法(非递归实现):
- 栈(Stack): 模拟 DFS 过程,存储当前路径节点。
- 链表(LinkedList): 存储最终结果,每次将没有出边的节点加入链表头部。
- 算法流程:
- 将起点 "JFK" 压入栈。
- 当栈非空时:
- 取栈顶节点
cur(不弹出)。 - 若
cur存在邻居(即优先队列非空),弹出最小邻居并压入栈。 - 若
cur无邻居,弹出cur并加入结果链表头部。
- 取栈顶节点
- 返回结果链表。
时间复杂度: O(n log n),其中 n 为边数。建图 O(n),Hierholzer 算法中每条边访问一次,堆操作 O(log n)。空间复杂度: O(n),存储图、栈和结果。
代码实现(Java版)🔥点击下载源码
java
class Solution {
public List<String> findItinerary(List<List<String>> tickets) {
// 构建图:出发机场 -> 到达机场的最小堆
Map<String, PriorityQueue<String>> graph = new HashMap<>();
for (List<String> ticket : tickets) {
String from = ticket.get(0);
String to = ticket.get(1);
graph.putIfAbsent(from, new PriorityQueue<>());
graph.get(from).offer(to);
}
// 使用栈进行 DFS 模拟
Deque<String> stack = new ArrayDeque<>();
LinkedList<String> result = new LinkedList<>(); // 结果链表(头部添加)
stack.push("JFK"); // 起点入栈
while (!stack.isEmpty()) {
String cur = stack.peek();
// 当前节点还有邻居
if (graph.containsKey(cur) && !graph.get(cur).isEmpty()) {
String next = graph.get(cur).poll(); // 取最小邻居
stack.push(next);
} else {
// 无邻居时,弹出节点并加入结果头部
result.addFirst(stack.pop());
}
}
return result;
}
}
代码说明
-
建图:
graph使用HashMap,键为出发机场,值为到达机场的PriorityQueue(最小堆)。- 遍历
tickets,将每个出发机场对应的到达机场加入最小堆,保证字典序。
-
Hierholzer 算法:
- 栈 (
stack): 存储当前访问路径,起点 "JFK" 入栈。 - 结果链表 (
result): 使用LinkedList支持头部插入。 - 循环处理:
- 若栈顶节点有邻居,弹出最小邻居并入栈。
- 若栈顶节点无邻居,弹出节点并加入
result头部(后序加入)。
- 最终
result即为欧拉路径(正序)。
- 栈 (
-
正确性保证:
- 最小堆确保每次选择字典序最小的邻居。
- 后序加入结果确保路径顺序正确(无出边的节点为路径终点)。
亮点: 使用 ArrayDeque 作为栈,操作高效;链表头部添加避免反转操作;最小堆确保字典序最小,且堆操作高效。
提交详情(执行用时、内存消耗)
