【困难】力扣算法题解析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版)🔥点击下载源码

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 作为栈,操作高效;链表头部添加避免反转操作;最小堆确保字典序最小,且堆操作高效。


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

相关推荐
一匹电信狗2 小时前
【LeetCode_21】合并两个有序链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl
User_芊芊君子2 小时前
【LeetCode经典题解】搞定二叉树最近公共祖先:递归法+栈存路径法,附代码实现
算法·leetcode·职场和发展
培风图南以星河揽胜2 小时前
Java版LeetCode热题100之零钱兑换:动态规划经典问题深度解析
java·leetcode·动态规划
算法_小学生2 小时前
LeetCode 热题 100(分享最简单易懂的Python代码!)
python·算法·leetcode
执着2592 小时前
力扣hot100 - 234、回文链表
算法·leetcode·链表
Gorgous—l2 小时前
数据结构算法学习:LeetCode热题100-多维动态规划篇(不同路径、最小路径和、最长回文子串、最长公共子序列、编辑距离)
数据结构·学习·算法
熬夜造bug2 小时前
LeetCode Hot100 刷题路线(Python版)
算法·leetcode·职场和发展
启山智软2 小时前
【中大企业选择源码部署商城系统】
java·spring·商城开发
2401_838472512 小时前
C++中的访问者模式
开发语言·c++·算法