HomeLeetcode332. Reconstruct Itinerary - Leetcode Solutions

332. Reconstruct Itinerary – Leetcode Solutions

Description

You are given a list of airline tickets where tickets[i] = [fromi, toi] represent the departure and the arrival airports of one flight. Reconstruct the itinerary in order and return it.

All of the tickets belong to a man who departs from "JFK", thus, the itinerary must begin with "JFK". If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string.

  • For example, the itinerary ["JFK", "LGA"] has a smaller lexical order than ["JFK", "LGB"].

You may assume all tickets form at least one valid itinerary. You must use all the tickets once and only once.

Examples:

Example 1:

Input: tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
Output: ["JFK","MUC","LHR","SFO","SJC"]

Example 2:

Input: tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Output: ["JFK","ATL","JFK","SFO","ATL","SFO"]

Solution in Python

Approach:

  1. Graph Representation:
    • Use a dictionary where the keys are the departure airports and the values are lists of arrival airports.
    • The lists of arrival airports should be maintained in lexicographical order for each departure airport to ensure that we pick the smallest airport first.
  2. Eulerian Path:
    • The task is to find an Eulerian path (a path that visits every edge exactly once) since we must use all tickets exactly once. This can be efficiently done using a hierarchical depth-first search (DFS) or by leveraging the stack-based Eulerian path construction (iterative DFS).
    • Start the DFS from “JFK”. When visiting a node (airport), recursively visit its neighbors (next destination). Once you exhaust all the neighbors, backtrack and add the airport to the result.
  3. Hierarchical DFS:
    • We visit airports in lexicographical order by sorting the destinations. Each time we visit an airport, we remove that edge from the graph.
    • Once all flights are used up, the backtrack will ensure that the itinerary is constructed in reverse order, so we reverse it before returning.
Python
from collections import defaultdict

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        # Build the adjacency list (graph)
        graph = defaultdict(list)
        
        # Sort tickets in reverse lexicographical order and build the graph
        # We reverse sort because we'll be popping destinations from the end of the list
        for from_airport, to_airport in sorted(tickets, reverse=True):
            graph[from_airport].append(to_airport)
        
        # Initialize result itinerary
        itinerary = []
        
        # Define the DFS function
        def dfs(airport):
            # While there are destinations left from this airport
            while graph[airport]:
                # Visit the next destination (smallest due to reverse sorting)
                next_airport = graph[airport].pop()
                dfs(next_airport)
            
            # Once all connections from this airport are visited, add to itinerary
            itinerary.append(airport)
        
        # Start the DFS from 'JFK'
        dfs("JFK")
        
        # The itinerary is built in reverse, so reverse it at the end
        return itinerary[::-1]

Explanation:

  1. Graph Construction:
    • We use a defaultdict(list) to represent the adjacency list of the graph. For each ticket, we store the destination airport as a neighbor of the departure airport.
    • We sort the tickets in reverse lexicographical order because we will be popping elements from the end of the list, and we want to ensure that the smallest destination comes first.
  2. Depth-First Search (DFS):
    • The dfs() function recursively visits each airport. For each airport, we pop the next destination (smallest lexicographical one) and recursively visit it.
    • Once all destinations from the current airport are visited, we append that airport to the itinerary.
  3. Itinerary Construction:
    • Since we are using DFS, the order of visited airports is reversed. The correct itinerary is obtained by reversing the result at the end.

Time Complexity:

  • O(E log E), where E is the number of edges (tickets). Sorting the tickets takes O(E log⁡ E), and the DFS visit for each ticket takes O(E). The overall complexity is dominated by the sorting step.

Space Complexity:

  • O(V + E), where V is the number of vertices (airports) and E is the number of edges (tickets). We store the graph in memory, which uses O(V+E) space. Additionally, the recursive DFS call stack may use up to O(V) space.

Solution in C++

C++
class Solution {
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        // Adjacency list where destinations are stored in sorted order
        unordered_map<string, multiset<string>> adj;
        
        // Populate the adjacency list (graph)
        for (auto& ticket : tickets) {
            adj[ticket[0]].insert(ticket[1]);
        }

        vector<string> itinerary;
        stack<string> dfsStack;
        dfsStack.push("JFK");

        while (!dfsStack.empty()) {
            string airport = dfsStack.top();

            // If there are no more destinations to explore from the current airport
            if (adj[airport].empty()) {
                itinerary.push_back(airport);
                dfsStack.pop();
            } else {
                // Visit the smallest lexicographical destination
                auto next_airport = adj[airport].begin();
                dfsStack.push(*next_airport);
                adj[airport].erase(next_airport);  // Remove the ticket (edge)
            }
        }

        // The itinerary is constructed in reverse order, so reverse it before returning
        reverse(itinerary.begin(), itinerary.end());
        return itinerary;
    }
};

Solution in Javascript

JavaScript
var findItinerary = function(tickets) {
    // Step 1: Create a graph where each airport (key) has a list of destination airports (values).
    // Sort destinations in lexical order so that we can always pick the smallest lexical destination first.
    let graph = {};

    tickets.forEach(([from, to]) => {
        if (!graph[from]) {
            graph[from] = [];
        }
        graph[from].push(to);
    });

    // Sort the adjacency list (destination airports) in lexical order for each airport.
    for (let airport in graph) {
        graph[airport].sort();
    }

    // Step 2: Initialize the result itinerary array and a stack for depth-first search (DFS).
    let itinerary = [];
    let stack = ['JFK']; // The itinerary must begin with "JFK" as per the problem.

    // Step 3: Perform DFS to build the itinerary.
    while (stack.length > 0) {
        let currentAirport = stack[stack.length - 1]; // Peek at the top of the stack

        // If the current airport has a destination to visit, we visit the next lexically smallest one.
        if (graph[currentAirport] && graph[currentAirport].length > 0) {
            stack.push(graph[currentAirport].shift()); // Pop the smallest destination from the adjacency list
        } else {
            // If there are no more destinations to visit from this airport, add it to the itinerary.
            itinerary.push(stack.pop());
        }
    }

    // The itinerary is constructed in reverse order, so we need to reverse it before returning.
    return itinerary.reverse();
};

Solution in Java

Java
import java.util.*;

class Solution {
    // This method reconstructs the itinerary from the tickets
    public List<String> findItinerary(List<List<String>> tickets) {
        // Graph to store the adjacency list where each departure airport maps to a priority queue of destination airports
        Map<String, PriorityQueue<String>> graph = new HashMap<>();
        
        // Build the graph (adjacency list) using the tickets
        for (List<String> ticket : tickets) {
            String from = ticket.get(0); // departure airport
            String to = ticket.get(1);   // destination airport
            
            // If the departure airport is not yet in the graph, add it
            graph.putIfAbsent(from, new PriorityQueue<>());
            
            // Add the destination airport to the priority queue of the departure airport
            graph.get(from).offer(to);
        }
        
        // List to store the final itinerary
        List<String> itinerary = new LinkedList<>();
        
        // Start DFS from 'JFK'
        dfs("JFK", graph, itinerary);
        
        // Since we added airports in reverse order during DFS, reverse the result list
        Collections.reverse(itinerary);
        return itinerary;
    }
    
    // Depth-first search function to traverse the graph
    private void dfs(String airport, Map<String, PriorityQueue<String>> graph, List<String> itinerary) {
        // While there are still destinations to visit from the current airport
        while (graph.containsKey(airport) && !graph.get(airport).isEmpty()) {
            // Get the lexicographically smallest destination airport
            String nextAirport = graph.get(airport).poll();
            
            // Recur on the next airport
            dfs(nextAirport, graph, itinerary);
        }
        
        // Post-order addition of the airport to the itinerary
        itinerary.add(airport);
    }
}

Subscribe
Notify of

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Popular