LeetCode #332 Hard

Reconstruct Itinerary

Reconstruct Itinerary: use every airline ticket once to build the lexicographically smallest itinerary starting at JFK.

Constraints
  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromi and toi consist of uppercase English letters.
  • fromi != toi
grapheulerian-pathdfs
Open on LeetCode ↗
02

Intuition

Reconstruct itinerary uses every airline ticket exactly once to build the lexicographically smallest route starting at JFK. Two things make it harder than it first appears. Greedily taking the smallest available destination at each step can strand you. You may reach an airport with no unused tickets while tickets elsewhere remain, and since every ticket must be used, that route is invalid — but you have already committed to it. The correct framing is that tickets are directed edges and the itinerary must use each exactly once. That is an Eulerian path, and the algorithm for it is Hierholzer's: - Follow edges greedily until stuck, then record airports on the way back out of the recursion. The insight is that getting stuck is not a failure. In a graph with an Eulerian path, the airport where you run out of tickets must be the final destination — no other vertex can strand you, because every other vertex has balanced in and out degrees. So appending airports as the recursion unwinds naturally places dead ends at the end of the route. That produces the path in reverse, so reverse it once at the finish. Lexicographic order comes free: always consume the smallest available destination first. A min-heap per airport, or a sorted list consumed from the front, handles that. Duplicate tickets between the same pair must stay as separate entries, since each is a distinct ticket that must be used.

How to spot this pattern

The phrase 'use every ticket/edge exactly once' signals an Eulerian trail rather than ordinary path search. When lexical minimality is also required, order each vertex's outgoing edges while running Hierholzer's algorithm.

03

Approach

Try it first

Before reading on: price up what the direct approach costs here, then ask what one visit per cell or vertex would have to mark so nothing is counted twice. Aim for O(E log E) time and O(E) space.

1

See why greedy alone fails

Taking the smallest destination each step can leave you stranded with tickets unused. The route must consume every ticket, so a dead end reached too early is invalid — which is what rules out a plain greedy walk.

2

Recognise it as an Eulerian path

Tickets are directed edges to be used exactly once, which is the definition of an Eulerian path. Naming it points straight at Hierholzer's algorithm rather than an ad-hoc search.

3

Store destinations in sorted order

Keep a min-heap or sorted list per airport so the smallest unused destination is always taken first. Duplicate tickets stay as separate entries — two tickets from A to B must both be consumed.

4

Consume tickets before recursing

Remove the ticket from the structure before the recursive call, not after. Leaving it in place lets the same ticket be used twice and produces a route longer than the ticket count.

5

Append on the way out

Add the current airport to the route only once it has no tickets left. A stuck airport must be the final destination in a graph with an Eulerian path, so post-order placement puts dead ends in exactly the right spot.

6

Reverse the result

The post-order construction builds the itinerary from the end backwards, so reverse it once at the finish. The reversed list starts at JFK and uses every ticket in lexically smallest order.

7

Cost of the algorithm

Sorting or heapifying the destinations costs O(E log E), and each edge is traversed exactly once, giving O(E log E) time overall with O(E) space for the adjacency structure and the route.

04

Solution & live demo

▶1class Solution:
▶2 def findItinerary(self, tickets:
▶3 List[List[str]]) -> List[str]:
▶4 graph = defaultdict(list)
▶5 for source, destination in tickets:
▶6 heappush(graph[source], destination)
▶7 
▶8 route = []
▶9 def visit(airport):
▶10 while graph[airport]:
▶11 visit(heappop(graph[airport]))
▶12 route.append(airport)
▶13 
▶14 visit('JFK')
▶15 return route[::-1]
05

Common pitfalls

Appending before exploring

✗ Wrong
route.append(airport)
visit(next_airport)
✓ Right
visit(next_airport)
route.append(airport)

Eulerian construction records vertices on backtracking so premature dead ends land at the end.

Using a set for destinations

✗ Wrong
graph[source].add(destination)
✓ Right
heappush(graph[source], destination)

A set destroys duplicate tickets, even though each duplicate must be consumed.

Returning postorder directly

✗ Wrong
return route
✓ Right
return route[::-1]

Airports are appended from the route's end back toward JFK.

06

Edge cases

Duplicate identical tickets

Each heap insertion is a separate edge and is popped exactly once.

The smallest immediate destination is a dead end

Postorder insertion delays that dead end instead of invalidating the remaining tour.

A single ticket from JFK

DFS appends the destination then JFK, and reversal returns both airports.

07

Complexity

Time
O(E log E)
Space
O(E)
Every ticket is inserted into and removed from one heap, then stored in the route.