Reconstruct Itinerary

Hard Graphs Eulerian Path (Hierholzer) Original on LeetCode

Reported atAmazonAirbnb

Reconstruct Itinerary is a hard graphs problem solved with the eulerian path (hierholzer) pattern. The best approach, optimal (hierholzer's algorithm), runs in O(E log E) time and O(E) space. Below are 2 approaches in Java, Python, C++ and JavaScript, from brute force (backtracking) up.

Problem

Each ticket tickets[i] = [from, to] is a one-way flight between two airports. A traveller starts at "JFK" and wants to use every ticket exactly once.

Return the itinerary as a list of airports in visiting order. If several itineraries use all the tickets, return the one that is smallest in lexical order when the airport codes are read as a sequence (compare the first airport that differs).

Examples

Example 1

Input
tickets = [["SEA","DEN"],["JFK","ORD"],["ORD","SEA"],["DEN","BOS"]]
Output
["JFK","ORD","SEA","DEN","BOS"]
Why
Only one order uses every ticket: the tickets simply chain together.

Example 2

Input
tickets = [["JFK","MIA"],["JFK","ATL"],["ATL","JFK"],["MIA","ATL"]]
Output
["JFK","ATL","JFK","MIA","ATL"]
Why
Both JFK -> MIA -> ATL -> JFK -> ATL and JFK -> ATL -> JFK -> MIA -> ATL use all tickets; the second is smaller in lexical order.

Example 3

Input
tickets = [["JFK","AAA"],["JFK","BBB"],["BBB","JFK"]]
Output
["JFK","BBB","JFK","AAA"]
Why
Flying to AAA first would strand you there with tickets left, so the itinerary must visit BBB first.

Constraints

  • 1 <= tickets.length <= 5000; each ticket is [from, to] with from != to.
  • Every airport code is exactly 3 uppercase letters.
  • The same ticket can appear more than once (each copy must be used).
  • At least one itinerary starting at "JFK" uses every ticket exactly once.

Solve it here

Write your solution in Java, Python, C++ or JavaScript and run it against the examples, right in the page. Submit runs the hidden tests too and, with a free account, records the solve in your revision log.

00:00
Testcase

Run checks the testcases above (3 examples, or your own). Submit also runs 11 hidden tests and saves the solve to your log. Signed in, every Run and Submit is saved under My submissions; your draft stays in this browser.

Solutions

Try it yourself first. Then compare: each approach lists its idea, the steps, its time and space, and the code in Java, Python, C++ and JavaScript.

ApproachTimeSpace
Brute force (backtracking)O(E^E) worst caseO(E)
Optimal (Hierholzer's algorithm)O(E log E)O(E)

1Brute force (backtracking)

TimeO(E^E) worst caseA dead end can force the search to back up and retry many orders of the same tickets.
SpaceO(E)The used flags, the route and the recursion stack are each at most E + 1 long.

Sort the tickets by destination and try them in that order with DFS. Mark a ticket used, recurse, and undo it if the branch cannot use every ticket. The first complete route found is the lexically smallest one.

  1. Sort tickets by destination; group ticket indexes by their origin.
  2. dfs(airport): if the route has n + 1 airports, succeed.
  3. For each unused ticket out of airport, in destination order: mark it, append the destination, recurse; on failure unmark and remove.
  4. Start from JFK and return the route.
class Solution {
    private List<String> route;
    private Map<String, List<Integer>> out;
    private List<List<String>> t;
    private boolean[] used;

    public List<String> findItinerary(List<List<String>> tickets) {
        t = new ArrayList<>(tickets);
        t.sort((a, b) -> a.get(1).compareTo(b.get(1)));
        out = new HashMap<>();
        for (int i = 0; i < t.size(); i++)
            out.computeIfAbsent(t.get(i).get(0), x -> new ArrayList<>()).add(i);
        used = new boolean[t.size()];
        route = new ArrayList<>();
        route.add("JFK");
        dfs("JFK");
        return route;
    }

    private boolean dfs(String at) {
        if (route.size() == t.size() + 1) return true;
        for (int i : out.getOrDefault(at, Collections.emptyList())) {
            if (used[i]) continue;
            used[i] = true;
            route.add(t.get(i).get(1));
            if (dfs(t.get(i).get(1))) return true;
            route.remove(route.size() - 1);
            used[i] = false;
        }
        return false;
    }
}

2Optimal (Hierholzer's algorithm)

TimeO(E log E)Sorting the destination lists dominates; the walk itself pushes and pops each ticket once.
SpaceO(E)The adjacency lists, the stack and the route all hold O(E) airports.

Keep each airport's destinations sorted so the smallest one can be taken next. Walk greedily with an explicit stack: while the top airport has an unused ticket, take the smallest and push its destination. When the top has no tickets left, it is final in what remains of the trip, so pop it into the route. The route comes out backwards.

  1. For each origin, collect destinations and sort them in reverse so the smallest is at the end (cheap to remove).
  2. stack = [JFK].
  3. While the stack is not empty: if top has destinations, remove the last (smallest) and push it; else pop top and append it to route.
  4. Reverse route and return it.
class Solution {
    public List<String> findItinerary(List<List<String>> tickets) {
        Map<String, List<String>> out = new HashMap<>();
        for (List<String> tk : tickets)
            out.computeIfAbsent(tk.get(0), x -> new ArrayList<>()).add(tk.get(1));
        for (List<String> l : out.values()) l.sort(Collections.reverseOrder());
        Deque<String> stack = new ArrayDeque<>();
        List<String> route = new ArrayList<>();
        stack.push("JFK");
        while (!stack.isEmpty()) {
            List<String> next = out.get(stack.peek());
            if (next != null && !next.isEmpty()) stack.push(next.remove(next.size() - 1));
            else route.add(stack.pop());
        }
        Collections.reverse(route);
        return route;
    }
}

Edge cases to test

  • A single ticket
  • Taking the lexically smallest destination leads to a dead end
  • Duplicate tickets between the same two airports
  • The trip passes through JFK several times

Hints

Hint 1

This is a path that uses every edge exactly once: an Eulerian path in a directed graph.

Hint 2

Greedily taking the smallest destination can get stuck, but the stuck airport must be the very end of the trip.

Hint 3

Hierholzer: follow the smallest unused ticket until you cannot move, then add the airport to the route on the way back. Reverse at the end.

FAQ

What is the best time complexity for Reconstruct Itinerary?

Optimal (Hierholzer's algorithm) runs in O(E log E) time and O(E) extra space. Sorting the destination lists dominates; the walk itself pushes and pops each ticket once.

Which pattern does Reconstruct Itinerary use?

It is a graphs problem that uses the eulerian path (hierholzer) pattern.

Is there a brute force solution for Reconstruct Itinerary?

Yes. Brute force (backtracking) takes O(E^E) worst case time and O(E) space. Sort the tickets by destination and try them in that order with DFS.

Which edge cases should I test for Reconstruct Itinerary?

A single ticket; Taking the lexically smallest destination leads to a dead end; Duplicate tickets between the same two airports; The trip passes through JFK several times.