150NeetCode 150

93.Reconstruct Itinerary

HardGraphs ยท 14/19

Problem

You are given a list of airline tickets where tickets[i] = [from, to] is a one-way flight. Reconstruct the itinerary in order and return it.

The trip always starts at "JFK" and you must use every ticket exactly once. If several valid itineraries exist, return the one that is smallest in lexical order when read as a single string. The input is guaranteed to admit at least one valid itinerary.

Examples

Input:  tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
Output: ["JFK","MUC","LHR","SFO","SJC"]
Explanation: There is only one way to chain all four tickets from JFK.
Input:  tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Output: ["JFK","ATL","JFK","SFO","ATL","SFO"]
Explanation: ["JFK","SFO","ATL","JFK","ATL","SFO"] is also valid but is lexically larger.
Input:  tickets = [["JFK","KUL"],["JFK","NRT"],["NRT","JFK"]]
Output: ["JFK","NRT","JFK","KUL"]
Explanation: Greedily flying to KUL first (smaller name) gets stuck, because KUL has no outgoing ticket.

Constraints

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • from.length == 3, to.length == 3, uppercase English letters
  • from != to
  • At least one valid itinerary exists.

๐Ÿ’ก Key Insight

Using every edge exactly once is an Eulerian path problem. Hierholzer's algorithm builds it in linear time: always take the smallest available outgoing edge, and when an airport has no tickets left, append it to the answer. Airports are therefore finalised in reverse โ€” a dead end (like KUL above) is pushed first and ends up last โ€” so reversing the post-order gives the lexically smallest valid itinerary without any backtracking.

Backtracking DFS

TimeO(E^d) in the worst caseSpaceO(E)

Intuition

Sort each airport's destinations. From JFK, try destinations in alphabetical order, consuming a ticket each time. If we have used all tickets, the current route is the answer (it is the lexically smallest because we tried choices in sorted order). If we get stuck early, undo the last ticket and try the next destination.

Algorithm

  1. Build adj: from -> sorted list of to.
  2. route = ["JFK"]. Call dfs("JFK").
  3. In dfs(airport): if route.size() == tickets.size() + 1 return true.
  4. For each index i in adj[airport]: remove destination dest = list.remove(i), append it to route, recurse; if the recursion succeeds return true; otherwise remove it from route and re-insert it at index i.
  5. Return false.

Visualization

tickets: JFK->KUL, JFK->NRT, NRT->JFK
adj: JFK:[KUL, NRT]  NRT:[JFK]

dfs(JFK) try KUL: route=[JFK,KUL], KUL has no tickets, 1 ticket used of 3 -> fail, undo
         try NRT: route=[JFK,NRT] -> dfs(NRT) try JFK: route=[JFK,NRT,JFK]
                  -> dfs(JFK) try KUL: route=[JFK,NRT,JFK,KUL], size 4 == 3+1 -> success

Code

java
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

class Solution {
    private final Map<String, List<String>> adj = new HashMap<>();
    private final LinkedList<String> route = new LinkedList<>();
    private int totalTickets;

    public List<String> findItinerary(List<List<String>> tickets) {
        totalTickets = tickets.size();
        for (List<String> t : tickets) {
            adj.computeIfAbsent(t.get(0), k -> new ArrayList<>()).add(t.get(1));
        }
        for (List<String> dests : adj.values()) {
            Collections.sort(dests); // try lexically smaller destinations first
        }
        route.add("JFK");
        dfs("JFK");
        return route;
    }

    private boolean dfs(String airport) {
        if (route.size() == totalTickets + 1) {
            return true; // every ticket used
        }
        List<String> dests = adj.get(airport);
        if (dests == null) {
            return false;
        }
        for (int i = 0; i < dests.size(); i++) {
            String next = dests.remove(i); // use this ticket
            route.add(next);
            if (dfs(next)) {
                return true;
            }
            route.removeLast(); // backtrack
            dests.add(i, next);
        }
        return false;
    }
}

Complexity

  • Time: O(E^d) in the worst case โ€” d is the max out-degree; backtracking can explore many partial routes. Sorting adds O(E log E).
  • Space: O(E) โ€” adjacency lists, route and recursion depth.

Complexity Comparison

Approach Time Space
Backtracking DFS Exponential worst case O(E)
Hierholzer's Algorithm O(E log E) O(E)

Common Pitfalls

  • Pure greedy without backtracking or post-order โ€” it flies to KUL first and gets stuck.
  • Using a Set for destinations: duplicate tickets (e.g. two JFK -> ATL) are legal and must both be used.
  • Appending airports in pre-order instead of post-order in Hierholzer's; the result must be built in reverse finishing order.
  • Forgetting that some airports have no outgoing tickets at all, so adj.get(airport) can be null.