332. Reconstruct Itinerary - Explanation

Problem Link

Description

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.

  • For example, the itinerary ["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 <= 300
  • from_i != to_i


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(ElogE) time and O(E) space, where E is the number of tickets (edges).


Hint 1

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?


Hint 2

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.


Hint 3

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?


Hint 4

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.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation (Adjacency List) - Building and traversing a directed graph from edge pairs
  • Depth First Search (DFS) - Recursive and iterative graph traversal with backtracking
  • Eulerian Path - Understanding when a path exists that uses every edge exactly once (Hierholzer's Algorithm)
  • Sorting - Needed to ensure lexicographically smallest result when multiple paths exist

Intuition

We must build an itinerary that:

  • starts from "JFK"
  • uses every ticket exactly once
  • is lexicographically smallest among all valid itineraries.

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.

Algorithm

  1. Sort tickets lexicographically.
  2. Build an adjacency list adj[src] = list of destinations in sorted order.
  3. Start res = ["JFK"].
  4. Run DFS from "JFK":
    • If len(res) == len(tickets) + 1, all tickets are used → return true.
    • For each possible destination v from src (in order):
      • Remove that edge (src -> v) from adj[src] (use the ticket).
      • Append v to res.
      • If DFS from v succeeds, return true.
      • Otherwise backtrack:
        • Remove v from res
        • Insert the destination back into adj[src] at the same position.
  5. Return 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 res

Time & Space Complexity

  • Time complexity: O(EV)O(E * V)
  • Space complexity: O(EV)O(E * V)

Where EE is the number of tickets (edges) and VV is the number of airports (vertices).


2. Hierholzer's Algorithm (Recursion)

Intuition

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:

  • always taking an available edge,
  • going as deep as possible,
  • and adding airports to the answer only when no outgoing edges remain.

To ensure the lexicographically smallest itinerary:

  • we sort tickets,
  • and always pick the smallest destination first.

The key idea:

Build the path in reverse while backtracking.

Algorithm

  1. Build an adjacency list where each airport maps to its destinations.
  2. Sort tickets in reverse lexicographical order and push destinations so we can pop the smallest later.
  3. Start DFS from "JFK":
    • While there are outgoing edges:
      • Remove one destination and DFS into it.
    • When no edges remain, add the current airport to the result.
  4. Reverse the result list to get the correct itinerary.
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]

Time & Space Complexity

  • Time complexity: O(ElogE)O(E\log E)
  • Space complexity: O(E)O(E)

Where EE is the number of tickets (edges) and VV is the number of airports (vertices).


3. Hierholzer's Algorithm (Iteration)

Intuition

This is the iterative version of Hierholzer’s Algorithm for finding an Eulerian Path.

We must:

  • use every ticket exactly once,
  • start from "JFK",
  • and return the lexicographically smallest valid itinerary.

Instead of recursion, we simulate the DFS using a stack:

  • Keep moving forward while tickets exist.
  • When stuck (no outgoing flights), backtrack and record the airport.

Key idea:

Airports are added to the answer only when they have no remaining outgoing edges.

Algorithm

  1. Build an adjacency list from tickets.
  2. Sort tickets in reverse lexicographical order so popping gives the smallest destination.
  3. Initialize a stack with "JFK".
  4. While the stack is not empty:
    • Look at the top airport:
      • If it has outgoing flights, pop one and push the destination.
      • Otherwise, pop the airport and add it to the result.
  5. Reverse the result to get the final itinerary.
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]

Time & Space Complexity

  • Time complexity: O(ElogE)O(E\log E)
  • Space complexity: O(E)O(E)

Where EE is the number of tickets (edges) and VV is the number of airports (vertices).


Common Pitfalls

Not Starting from JFK

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.

Forgetting to Use Each Ticket Exactly Once

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.

Sorting in Wrong Order for Hierholzer's Algorithm

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.

Not Reversing the Result in Post-Order Traversal

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.

Confusing Dead Ends with Valid Endpoints

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.