Description

給定一組機票 tickets,每張票 [from, to] 代表一段航程。所有機票都要用完,從 "JFK" 出發,回傳字典序最小的行程。保證至少存在一個合法行程。

Example:

Input: tickets = [[“MUC”,“LHR”],[“JFK”,“MUC”],[“SFO”,“SJC”],[“LHR”,“SFO”]] Output: [“JFK”,“MUC”,“LHR”,“SFO”,“SJC”]

Intuition#

這是尤拉路徑 (Eulerian Path) 問題 – 走遍所有邊恰好一次。用 Hierholzer 演算法後序插入。

  • 每張機票是一條有向邊,要走遍所有邊恰好一次
  • 使用有序集合(PriorityQueue 或排序後的列表)保證字典序最小
  • Hierholzer 演算法:DFS 到死路時將節點加入結果(後序),最後反轉

Approaches#

1: DFS + 回溯#

  • 概念: 從 JFK 出發做 DFS,每次選字典序最小的下一站,若無法用完所有票就回溯
  • 時間複雜度: O(E^d)(回溯最壞情況,d 為遞迴深度)
  • 空間複雜度: O(V + E)
class Solution {
    fun findItinerary(tickets: List<List<String>>): List<String> {
        val graph = HashMap<String, MutableList<String>>()
        for ((from, to) in tickets) {
            graph.getOrPut(from) { mutableListOf() }.add(to)
        }
        for (key in graph.keys) graph[key]!!.sort()

        val result = mutableListOf("JFK")
        val totalEdges = tickets.size

        fun backtrack(): Boolean {
            if (result.size == totalEdges + 1) return true
            val cur = result.last()
            val neighbors = graph[cur] ?: return false
            for (i in neighbors.indices) {
                val next = neighbors[i]
                neighbors.removeAt(i)
                result.add(next)
                if (backtrack()) return true
                result.removeAt(result.lastIndex)
                neighbors.add(i, next)
            }
            return false
        }

        backtrack()
        return result
    }
}

⭐2: Hierholzer 演算法(尤拉路徑)#

  • 概念: DFS 走到死路時才加入結果(後序),使用 PriorityQueue 保證字典序,最後反轉
  • 時間複雜度: O(E log E)(PriorityQueue 操作)
  • 空間複雜度: O(V + E)
class Solution {
    fun findItinerary(tickets: List<List<String>>): List<String> {
        val graph = HashMap<String, PriorityQueue<String>>()
        for ((from, to) in tickets) {
            graph.getOrPut(from) { PriorityQueue() }.add(to)
        }

        val result = LinkedList<String>()

        fun dfs(airport: String) {
            val destinations = graph[airport]
            while (destinations != null && destinations.isNotEmpty()) {
                dfs(destinations.poll())
            }
            result.addFirst(airport)
        }

        dfs("JFK")
        return result
    }
}

🔑 Takeaways#

  • Pattern: 尤拉路徑 – Hierholzer 演算法,後序加入 + 反轉
  • 關鍵技巧: 使用 PriorityQueue 自動排序保證字典序最小;result.addFirst() 相當於後序反轉