【困难】力扣算法题解析LeetCode332:重新安排行程

关注文末推广名片,即可免费获得本题测试源码

题目来源:LeetCode332:重新安排行程

问题抽象: 给定机票列表 ticketstickets[i] = [from_i, to_i] 表示从 from_i 机场飞往 to_i 机场的行程),要求 重建行程路线 使其满足以下核心需求:

  1. 行程规则

    • 起点固定:路线必须从 "JFK" 机场开始;
    • 全覆盖:所有机票 必须被使用且仅使用一次(每条边遍历一次);
    • 字典序最小:若存在多条可行路线,返回 字典序最小 的行程(如 ["JFK","ABC"] 优先于 ["JFK","ACB"])。
  2. 输入约束

    • 机票数量 ∈ [1, 300]
    • 机场代码为 3个大写字母(如 "JFK", "SFO");
    • 保证至少存在一条可行路线(无需处理无解场景)。
  3. 图结构特性

    • 机票表示 有向边from_i → to_i);
    • 路线需形成 欧拉路径(所有边遍历一次);
    • 可能存在 多重边(同一航线多张机票)。
  4. 输出要求

    • 返回机场代码列表(按行程顺序排列);
    • 示例:
      • 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"](字典序最小)。
  5. 关键挑战

    • 字典序优化:每个机场的下一站需按字典序排序(优先选 "ABC" 而非 "ACB");
    • 环检测:需处理 局部环(如 JFK→NRT→JFK→KUL);
    • 多重边消耗:同航线多张机票需独立消耗。

输入:二维字符串数组 tickets(如 [["JFK","SFO"]]
输出:字符串列表(完整行程路线)。


解题思路

题目本质是寻找有向图的欧拉路径(Eulerian Path),即一条访问图中每条边恰好一次的路径。要求从 JFK 出发,且返回字典序最小的行程。Hierholzer 算法可高效求解欧拉路径,结合贪心策略(每次选择字典序最小的邻居)确保结果字典序最小。

关键步骤:

  1. 建图:

    • 使用 HashMap 存储每个出发机场对应的到达机场列表。
    • 为每个出发机场的到达机场列表使用最小堆(PriorityQueue),确保每次取字典序最小的邻居。
  2. Hierholzer 算法(非递归实现):

    • 栈(Stack): 模拟 DFS 过程,存储当前路径节点。
    • 链表(LinkedList): 存储最终结果,每次将没有出边的节点加入链表头部。
    • 算法流程:
      1. 将起点 “JFK” 压入栈。
      2. 当栈非空时:
        • 取栈顶节点 cur(不弹出)。
        • cur 存在邻居(即优先队列非空),弹出最小邻居并压入栈。
        • cur 无邻居,弹出 cur 并加入结果链表头部。
      3. 返回结果链表。

时间复杂度: O(n log n),其中 n 为边数。建图 O(n),Hierholzer 算法中每条边访问一次,堆操作 O(log n)。空间复杂度: O(n),存储图、栈和结果。


代码实现(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;
    }
}

代码说明

  1. 建图:

    • graph 使用 HashMap,键为出发机场,值为到达机场的 PriorityQueue(最小堆)。
    • 遍历 tickets,将每个出发机场对应的到达机场加入最小堆,保证字典序。
  2. Hierholzer 算法:

    • 栈 (stack): 存储当前访问路径,起点 “JFK” 入栈。
    • 结果链表 (result): 使用 LinkedList 支持头部插入。
    • 循环处理:
      • 若栈顶节点有邻居,弹出最小邻居并入栈。
      • 若栈顶节点无邻居,弹出节点并加入 result 头部(后序加入)。
    • 最终 result 即为欧拉路径(正序)。
  3. 正确性保证:

    • 最小堆确保每次选择字典序最小的邻居。
    • 后序加入结果确保路径顺序正确(无出边的节点为路径终点)。

亮点: 使用 ArrayDeque 作为栈,操作高效;链表头部添加避免反转操作;最小堆确保字典序最小,且堆操作高效。


提交详情(执行用时、内存消耗)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值