You are given a list of flight tickets tickets where tickets[i] = [from_i, to_i] represent the source airport and the destination airport.
Each from_i and to_i consists of three uppercase English letters.
Reconstruct the itinerary in order and return it.
All of the tickets belong to someone who originally departed from "JFK". Your objective is to reconstruct the flight path that this person took, assuming each ticket was used exactly once.
If there are multiple valid flight paths, return the lexicographically smallest one.
["JFK", "SEA"] has a smaller lexical order than ["JFK", "SFO"].You may assume all the tickets form at least one valid flight path.
Example 1:
Input: tickets = [["BUF","HOU"],["HOU","SEA"],["JFK","BUF"]]
Output: ["JFK","BUF","HOU","SEA"]Example 2:
Input: tickets = [["HOU","JFK"],["SEA","JFK"],["JFK","SEA"],["JFK","HOU"]]
Output: ["JFK","HOU","JFK","SEA","JFK"]Explanation: Another possible reconstruction is ["JFK","SEA","JFK","HOU","JFK"] but it is lexicographically larger.
Constraints:
1 <= tickets.length <= 300from_i != to_i
You should aim for a solution with O(ElogE) time and O(E) space, where E is the number of tickets (edges).
Consider this problem as a graph where airports are the nodes and tickets are the edges. Since we need to utilize all the tickets exactly once, can you think of an algorithm that visits every edge exactly once? Additionally, how do you ensure the smallest lexical order is maintained?
We build an adjacency list from the given tickets, which represent directed edges. We perform a DFS to construct the result, but we first sort the neighbors' list of each node to ensure the smallest lexical order. Why? Sorting guarantees that during DFS, we visit the node with the smallest lexical order first.
DFS would be a naive solution, as it takes O(E * V) time, where E is the number of tickets (edges) and V is the number of airports (nodes). In this approach, we traverse from the given source airport JFK, perform DFS by removing the neighbor, traversing it, and then reinserting it back. Can you think of a better way? Perhaps an advanced algorithm that incorporates DFS might be helpful?
We can use Hierholzer's algorithm, a modified DFS approach. Instead of appending the node to the result list immediately, we first visit all its neighbors. This results in a post-order traversal. After completing all the DFS calls, we reverse the path to obtain the final path, which is also called Euler's path.
Before attempting this problem, you should be comfortable with:
We must build an itinerary that:
"JFK"This DFS solution tries destinations in sorted order.
At each airport src, we choose one outgoing ticket, remove it (so it can't be reused), and continue DFS.
If we reach a dead end before using all tickets, we backtrack: undo the choice and try the next destination.
Sorting tickets ensures the first complete valid path we find is the smallest lexicographically.
tickets lexicographically.adj[src] = list of destinations in sorted order.res = ["JFK"]."JFK":len(res) == len(tickets) + 1, all tickets are used → return true.v from src (in order):src -> v) from adj[src] (use the ticket).v to res.v succeeds, return true.v from resadj[src] at the same position.res.class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
adj = {src: [] for src, dst in tickets}
tickets.sort()
for src, dst in tickets:
adj[src].append(dst)
res = ["JFK"]
def dfs(src):
if len(res) == len(tickets) + 1:
return True
if src not in adj:
return False
temp = list(adj[src])
for i, v in enumerate(temp):
adj[src].pop(i)
res.append(v)
if dfs(v): return True
adj[src].insert(i, v)
res.pop()
return False
dfs("JFK")
return resWhere is the number of tickets (edges) and is the number of airports (vertices).
This problem is an Eulerian Path problem:
we must use every ticket exactly once and form a valid path starting from "JFK".
Hierholzer's Algorithm builds such a path by:
To ensure the lexicographically smallest itinerary:
The key idea:
Build the path in reverse while backtracking.
"JFK":class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
adj = defaultdict(list)
for src, dst in sorted(tickets)[::-1]:
adj[src].append(dst)
res = []
def dfs(src):
while adj[src]:
dst = adj[src].pop()
dfs(dst)
res.append(src)
dfs('JFK')
return res[::-1]Where is the number of tickets (edges) and is the number of airports (vertices).
This is the iterative version of Hierholzer’s Algorithm for finding an Eulerian Path.
We must:
"JFK",Instead of recursion, we simulate the DFS using a stack:
Key idea:
Airports are added to the answer only when they have no remaining outgoing edges.
"JFK".class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
adj = defaultdict(list)
for src, dst in sorted(tickets)[::-1]:
adj[src].append(dst)
stack = ["JFK"]
res = []
while stack:
curr = stack[-1]
if not adj[curr]:
res.append(stack.pop())
else:
stack.append(adj[curr].pop())
return res[::-1]Where is the number of tickets (edges) and is the number of airports (vertices).
The problem explicitly states the journey must begin from "JFK". Forgetting to initialize the traversal from "JFK" or accidentally starting from a different airport will produce an invalid itinerary.
Each ticket represents a one-time-use flight. Failing to remove or mark tickets as used after consuming them leads to infinite loops or itineraries that reuse the same flight multiple times.
Hierholzer's algorithm requires processing destinations in reverse lexicographical order (when using a stack/pop approach) so that popping gives the smallest destination. Sorting in ascending order without reversing, or using the wrong data structure, produces lexicographically incorrect results.
In Hierholzer's algorithm, airports are added to the result when backtracking (post-order), which builds the path in reverse. Forgetting to reverse the final result returns the itinerary in wrong order.
In the DFS backtracking approach, reaching an airport with no outgoing flights does not necessarily mean failure. It could be the valid end of the Eulerian path. Only return failure if you cannot use all tickets, not simply because you reached a node with no remaining edges.