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:
- The itinerary must start with "JFK," as it represents the departure point.
- 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:
-
We have a list of airline tickets, where each ticket is represented as a pair of airports
[from, to]
. -
The itinerary must start with "JFK."
-
If there are multiple valid itineraries, we should return the one with the smallest lexical order when read as a single string.
-
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 returnFalse
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 = ["JFK"] # Start with JFK
num_tickets = len(tickets) # Total number of tickets
dfs("JFK", 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 thenum_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:
- 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.
- 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.
- 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.
- 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!