Press enter to see results or esc to cancel.

Reconstruct Itinerary Leetcode Problem 332 [Python Solution]

Welcome to another coding adventure!

In this blog post, we'll tackle a challenging problem called "Reconstruct Itinerary," a problem from LeetCode.

We will provide a Python solution that efficiently reconstructs an itinerary given a list of airline tickets.

Problem Overview

Imagine you are given a list of airline tickets, where each ticket represents a flight from one airport to another.

These tickets are represented as pairs [from, to], indicating the departure and arrival airports.

Your task is to reconstruct the itinerary, starting from "JFK" (John F.

Kennedy International Airport) and returning it in the order in which the flights occurred.

There are a few important rules to consider:

  1. The itinerary must start with "JFK," as it represents the departure point.
  2. If there are multiple valid itineraries, you should return the one with the smallest lexical order when read as a single string.

In other words, you need to find the itinerary that comes first in a sorted order.
3. You must use all the tickets exactly once, and no ticket should be used more than once.

Let's illustrate this with a couple of 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"]

In Example 2, there are multiple valid itineraries.

One possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"].

However, this itinerary is not in the smallest lexical order.

The correct answer, as per the problem's requirements, is ["JFK","ATL","JFK","SFO","ATL","SFO"].

Constraints

Before we delve into the solution, it's important to understand the constraints of this problem:

  • 1 <= tickets.length <= 300
  • Each ticket is represented as a pair [from, to].
  • Both "from" and "to" consist of three uppercase English letters.
  • "from" and "to" are not equal, meaning that you cannot have a flight from an airport to itself.

Now that we have a clear understanding of the problem, let's break it down into smaller components and tackle it step by step.

Understanding the Problem

To solve this problem, we need to build a solution that effectively reconstructs an itinerary given a list of airline tickets.

However, we'll approach this problem in a structured manner, breaking it down into specific sections.

Let's start by understanding the key components and constraints.

Example 1:

Let's take a closer look at the first example to better understand how the problem works:

Input:

tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]

Output:

["JFK","MUC","LHR","SFO","SJC"]

In this example, we are given a list of tickets, each representing a flight from one airport to another.

We need to reconstruct the itinerary starting from "JFK" and return it in order.

Components of the Problem:

  1. We have a list of airline tickets, where each ticket is represented as a pair of airports [from, to].

  2. The itinerary must start with "JFK."

  3. If there are multiple valid itineraries, we should return the one with the smallest lexical order when read as a single string.

  4. Each ticket must be used exactly once, and no ticket should be used more than once.

Key Terminology:

  • Itinerary: The sequence of airports visited in the correct order.
  • Lexical Order: The order in which strings are arranged alphabetically.

Now that we have a clear understanding of the problem statement and its components, let's explore the constraints and edge cases that a valid solution must consider.

Constraints

Understanding the constraints is essential to ensure that our solution works efficiently for various inputs.

Let's review the constraints of this problem:

  • The number of tickets (flights) can vary, but it will be at least one.

  • Each ticket is represented as a pair [from, to], where both "from" and "to" consist of three uppercase English letters.

This ensures that the airports' names are consistent.

  • The "from" airport (departure airport) will not be the same as the "to" airport (arrival airport).

You cannot have a flight from an airport to itself.

Now that we have a solid grasp of the problem and its constraints, we can move on to exploring the possible approaches to solving it.

Problem Solution

Solving this problem involves finding a valid itinerary, starting from "JFK" and using each ticket exactly once.

Since there can be multiple valid itineraries, we need to return the one with the smallest lexical order.

We'll tackle this problem in several steps, providing detailed explanations and Python code for each step.

Step 1: Build the Adjacency List

To effectively navigate the flights, we need to construct an adjacency list that represents the connections between airports.

The adjacency list will allow us to easily determine the next airport to visit from a given airport.

Here's how we can build the adjacency list:

def build_adjacency_list(tickets):
    adjacency = {}
    for from_airport, to_airport in tickets:
        if from_airport not in adjacency:
            adjacency[from_airport] = []
        adjacency[from_airport].append(to_airport)
    return adjacency

In this function, tickets is the list of airline tickets, and adjacency is a dictionary where each airport (represented by the "from" airport) is associated with a list of airports it can connect to.

We iterate through the tickets, creating entries in the adjacency dictionary as needed.

Step 2: Sort the Adjacency Lists

To ensure that we choose the next airport in lexical order when multiple options are available, we need to sort the adjacency lists.

This sorting is crucial to follow the rule of selecting the smallest lexical order itinerary.

We sort the lists by their destination airports.

Here's how it's done:

def sort_adjacency_lists(adjacency):
    for from_airport in adjacency:
        adjacency[from_airport].sort()

In this function, we iterate through the airports in the adjacency dictionary and sort the list of destination airports for each airport.

This sorting is essential for making consistent choices during the itinerary construction.

Step 3: Implement the Depth-First Search (DFS)

Now comes the heart of the solution—the depth-first search (DFS) algorithm.

We'll create a recursive DFS function that explores possible flight paths, ensuring that we visit each airport exactly once.

Here's the DFS function:

def dfs(current_airport, adjacency, itinerary, num_tickets):
    if len(itinerary) == num_tickets + 1:


        # If we've visited all airports, return the itinerary
        return True

    if current_airport not in adjacency:
        return False

    # Create a copy of the adjacency list for the current airport
    destinations = list(adjacency[current_airport])
    
    for destination in destinations:
        # Remove the destination from the list to backtrack if needed
        adjacency[current_airport].remove(destination)
        
        # Add the destination to the itinerary
        itinerary.append(destination)
        
        if dfs(destination, adjacency, itinerary, num_tickets):
            return True
        
        # If the current path didn't lead to a valid solution, backtrack
        itinerary.pop()
        adjacency[current_airport].append(destination)
    
    return False

In this DFS function:

  • We check if we've visited all airports (determined by the length of the itinerary).

If so, we return True to indicate that we've found a valid itinerary.

  • If the current airport doesn't have any outgoing flights (not in the adjacency list), we return False as it's a dead-end.

  • We make a copy of the adjacency list for the current airport to ensure that we backtrack properly if needed.

  • We iterate through the possible destinations from the current airport.

As we visit each destination, we temporarily remove it from the adjacency list and add it to the itinerary.

  • We recursively call the DFS function with the new destination.

If this path leads to a valid solution, we return True.

  • If the current path doesn't lead to a valid solution, we backtrack by removing the destination from the itinerary and adding it back to the adjacency list.

  • Finally, we return False if no valid itinerary is found in the current path.

Step 4: Start the DFS

We initiate the DFS process by calling the DFS function with "JFK" as the starting airport and passing the adjacency list, the initial itinerary, and the total number of tickets.

Here's how it's done:

def findItinerary(tickets):
    adjacency = build_adjacency_list(tickets)
    sort_adjacency_lists(adjacency)

    itinerary = [&quot;JFK&quot;]  # Start with JFK
    num_tickets = len(tickets)  # Total number of tickets

    dfs(&quot;JFK&quot;, adjacency, itinerary, num_tickets)

    return itinerary

In this final step, we:

  • Build the adjacency list using the build_adjacency_list function.

  • Sort the adjacency lists in lexical order using the sort_adjacency_lists function.

  • Initialize the itinerary with "JFK" as the starting airport and compute the num_tickets as the total number of tickets.

  • Initiate the DFS by calling it with "JFK" as the current airport and passing the adjacency list, the itinerary, and the number of tickets.

  • Finally, return the constructed itinerary.

This completes the implementation of the solution to the Reconstruct Itinerary problem.

The DFS algorithm ensures that we explore all possible flight paths while backtracking when necessary to construct a valid itinerary.

Time and Space Complexity

Let's analyze the time and space complexity of our solution to understand its efficiency.

Time Complexity:

The time complexity of our solution depends on the number of tickets (flights) and the number of airports (vertices) in the problem.

Since we're using a depth-first search (DFS) algorithm, the time complexity is approximately O(V^2), where V represents the number of vertices (airports).

Space Complexity:

The space complexity is determined by the memory required for our adjacency list and the recursive function call stack.

The space complexity can be considered as O(E) since we store the adjacency list, where E represents the number of edges (flights).

The depth of the recursive call stack is also determined by the number of edges.

Reasoning Behind Our Approach

Our approach to solving the Reconstruct Itinerary problem is based on constructing a valid itinerary starting from "JFK" using a depth-first search (DFS) algorithm.

Let's recap the key ideas behind our approach:

  1. Adjacency List: We create an adjacency list that represents the connections between airports, ensuring that each list of destinations is sorted in lexical order.

This helps us make consistent choices during the itinerary construction.

  1. DFS Algorithm: We implement a depth-first search (DFS) algorithm to explore possible flight paths, visiting each airport exactly once.

The DFS function backtracks when necessary to find a valid itinerary.

This approach ensures that we exhaustively search for a valid solution.

  1. Backtracking: Backtracking is a crucial part of our approach.

When we encounter a dead-end or a path that doesn't lead to a valid solution, we backtrack by removing the last added destination from the itinerary and adding it back to the adjacency list.

This allows us to explore alternative paths.

  1. Time Complexity: The time complexity of our solution is approximately O(V^2), where V represents the number of airports (vertices).

This is due to the depth-first search (DFS) and backtracking process.

While this may seem high, it is efficient given the problem's constraints.

By following these key ideas, we successfully address the constraints of the problem, constructing a valid itinerary starting from "JFK" and adhering to the rule of selecting the smallest lexical order itinerary when multiple valid solutions exist.

Related Interview Questions By Company:

Related Interview Questions By Difficulty:

Related Interview Questions By Category:

Conclusion

In this blog post, we tackled

the Reconstruct Itinerary problem, which falls under the category of advanced graphs and is considered a hard problem on LeetCode.

We presented a Python solution that constructs a valid itinerary starting from "JFK" by using a depth-first search (DFS) algorithm.

We carefully considered the sorting of adjacency lists to ensure consistent choices and explored alternative paths through backtracking.

Our solution offers an efficient way to find valid itineraries, considering the constraints of the problem, including the requirement to return the smallest lexical order itinerary when multiple solutions exist.

The time complexity of our solution is O(V^2), where V represents the number of airports, making it a practical and effective approach for solving the Reconstruct Itinerary problem.

If you found this guide helpful, please like and engage for more algorithm and coding tutorials.

Feel free to comment, ask questions, and share your suggestions.

We encourage you to explore and practice this problem further to deepen your understanding of graphs and algorithmic problem-solving.

For the full problem description and to try the solution yourself, you can visit the Reconstruct Itinerary problem on LeetCode.

Thank you for joining us in solving this challenging problem, and we look forward to sharing more coding adventures with you in the future.

Happy coding!

>