Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
* [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_islands/union_find.py)
* Number Of Provinces
* [Test Number Of Provinces](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_provinces/test_number_of_provinces.py)
* Reconstruct Itinerary
* [Test Reconstruct Itinerary](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/reconstruct_itinerary/test_reconstruct_itinerary.py)
* Reorder Routes
* [Test Reorder Routes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/reorder_routes/test_reorder_routes.py)
* Rotting Oranges
Expand Down Expand Up @@ -321,6 +323,8 @@
* [Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/linked_list_utils.py)
* Mergeklinkedlists
* [Test Merge](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/mergeklinkedlists/test_merge.py)
* Reorder List
* [Test Reorder List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/reorder_list/test_reorder_list.py)
* Singly Linked List
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/node.py)
* [Single Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/single_linked_list.py)
Expand Down Expand Up @@ -961,8 +965,6 @@
* [Test Longest Consecutive Sequence](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_longest_consecutive_sequence.py)
* [Test Max Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_max_subarray.py)
* [Test Zig Zag Sequence](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_zig_zag_sequence.py)
* Linked List
* [Test Reorder List](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/linked_list/test_reorder_list.py)
* [Test Build One 2 3](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/test_build_one_2_3.py)
* [Test Consecutive](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/test_consecutive.py)
* [Test Data Reverse](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/test_data_reverse.py)
Expand Down
109 changes: 109 additions & 0 deletions algorithms/graphs/reconstruct_itinerary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Reconstruct Itinerary

Given a list of airline tickets where tickets[i] = [fromi, toi] represent a departure airport and an arrival airport of
a single flight, reconstruct the itinerary in the correct order and return it.

The person who owns these tickets always starts their journey from "JFK". Therefore, the itinerary must begin with "JFK".
If there are multiple valid itineraries, you should prioritize the one with the smallest lexical order when considering
a single string.

> Lexicographical order is a way of sorting similar to how words are arranged in a dictionary. It compares items
> character by character, based on their order in the alphabet or numerical value.

- For example, the itinerary ["JFK", "EDU"] has a smaller lexical order than ["JFK", "EDX"].

> Note: You may assume all tickets form at least one valid itinerary. You must use all the tickets exactly once.

## Constraints

- 1 <= `tickets.length` <= 300
- `tickets[i].length` == 2
- `fromi.length` == 3
- `toi.length` == 3
- `fromi` != `toi`
- `fromi` and `toi` consist of upper case English letters

## Examples

![Example 1](./images/examples/reconstruct_itinerary_example_1.png)
![Example 2](./images/examples/reconstruct_itinerary_example_2.png)
![Example 3](./images/examples/reconstruct_itinerary_example_3.png)

## Related Topics

- Depth First Search
Comment thread
BrianLusina marked this conversation as resolved.
Outdated
- Graph
- Eulerian Circuit

## Solution

The algorithm uses __Hierholzer’s algorithm__ to reconstruct travel itineraries from a list of airline tickets. This
problem is like finding an __Eulerian path__ but with a fixed starting point, “JFK”. Hierholzer’s algorithm is great for
finding Eulerian paths and cycles, which is why we use it here.

> Hierholzer's algorithm is a method for finding an Eulerian circuit (a cycle that visits every edge exactly once) in a
> graph. It starts from any vertex and follows edges until it returns to the starting vertex, forming a cycle. If there
> are any unvisited edges, it starts a new cycle from a vertex on the existing cycle that has unvisited edges and merges
> the cycles. The process continues until all edges are visited.

> An Eulerian path is a trail in a graph that visits every edge exactly once. An Eulerian path can exist only if exactly
> zero or two vertices have an odd degree. If there are exactly zero vertices with an odd degree, the path can form a
> circuit (Eulerian circuit), where the starting and ending points are the same. If there are exactly two vertices with
> an odd degree, the path starts at one of these vertices and ends at the other.

The algorithm starts by arranging the destinations in reverse lexicographical order to ensure we always choose the
smallest destination first. It then uses depth-first search (DFS) starting from “JFK” to navigate the flights. As it
explores each flight path, it builds the itinerary by appending each visited airport when there are no more destinations
to visit from that airport. Since the airports are added in reverse order during this process, the final step is to
reverse the list to get the correct itinerary.

The basic algorithm to solve this problem will be:

1. Create a dictionary, `flight_map`, to store the flight information. Each key represents an airport; its corresponding
value is a list of destinations from that airport.
2. Initialize an empty list, result, to store the reconstructed itinerary.
3. Sort the destinations lexicographically in reverse order to ensure that the smallest destination is chosen first.
4. Perform DFS traversal starting from the airport "JFK".
- Get the list of destinations for the current airport from flight_map.
- While there are destinations available:
- Pop the next_destination from destinations.
- Recursively explore all available flights starting from the popped next_destination, until all possible flights
have been considered.
- Append the current airport to the result list.
5. Return the result list in reverse order to ensure the itinerary starts from the initial airport, "JFK", and proceeds
through the subsequent airports in the correct order.

Let’s look at the following illustration to get a better understanding of the solution:

![Solution 1](./images/solutions/reconstruct_itinerary_solution_1.png)
![Solution 2](./images/solutions/reconstruct_itinerary_solution_2.png)
![Solution 3](./images/solutions/reconstruct_itinerary_solution_3.png)
![Solution 4](./images/solutions/reconstruct_itinerary_solution_4.png)
![Solution 5](./images/solutions/reconstruct_itinerary_solution_5.png)
![Solution 6](./images/solutions/reconstruct_itinerary_solution_6.png)
![Solution 7](./images/solutions/reconstruct_itinerary_solution_7.png)
![Solution 8](./images/solutions/reconstruct_itinerary_solution_8.png)
![Solution 9](./images/solutions/reconstruct_itinerary_solution_9.png)
![Solution 10](./images/solutions/reconstruct_itinerary_solution_10.png)
![Solution 11](./images/solutions/reconstruct_itinerary_solution_11.png)
![Solution 12](./images/solutions/reconstruct_itinerary_solution_12.png)
![Solution 13](./images/solutions/reconstruct_itinerary_solution_13.png)
![Solution 14](./images/solutions/reconstruct_itinerary_solution_14.png)
![Solution 15](./images/solutions/reconstruct_itinerary_solution_15.png)

### Time Complexity

Each edge (flight) is traversed once during the DFS process in the algorithm, resulting in a complexity proportional to
the number of edges, ∣E∣.
Before DFS, the outgoing edges for each airport must be sorted. The sorting operation’s complexity depends on the input
graph’s structure.
In the worst-case scenario, such as a highly unbalanced graph (e.g., star-shaped), where one airport (e.g., JFK)
dominates the majority of flights, the sorting operation on this airport becomes highly expensive, possibly reaching N log N
complexity where `N = |E|/2` In a more balanced or average scenario, where each airport has a roughly equal number of
outgoing flights, the sorting operation complexity remains O(N log N) where N represents half of the total number of
edges divided by twice the number of airports O(|E|/2|V|). Thus, the algorithm’s overall complexity is O(|E|log|E/2|),
emphasizing the significance of the sorting operation in determining its performance.

### Space Complexity

The space complexity is O(∣V∣+∣E∣), where ∣V∣ is the number of airports and ∣E∣ is the number of flights.
80 changes: 80 additions & 0 deletions algorithms/graphs/reconstruct_itinerary/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import List, DefaultDict
from collections import defaultdict


def find_itinerary(tickets: List[List[str]]) -> List[str]:
"""
Reconstructs the itinerary from a given list of lists of tickets.
Args:
tickets (list): list of lists of tickets
Returns:
List[str]: reconstructed tickets
"""
if len(tickets) == 0:
return []

graph = {}
# Create a graph for each airport and keep list of airport reachable from it
for src, dst in tickets:
if src in graph:
graph[src].append(dst)
else:
graph[src] = [dst]

for src in graph.keys():
graph[src].sort(reverse=True)
# Sort children list in descending order so that we can pop last element
# instead of pop out first element which is costly operation
stack = []
res = []
stack.append("JFK")
# Start with JFK as starting airport and keep adding the next child to traverse
# for the last airport at the top of the stack. If we reach to an airport from where
# we can't go further then add it to the result. This airport should be the last to go
# since we can't go anywhere from here. That's why we return the reverse of the result
# After this backtrack to the top airport in the stack and continue to traaverse it's children
Comment thread
BrianLusina marked this conversation as resolved.
Outdated

while len(stack) > 0:
elem = stack[-1]
if elem in graph and len(graph[elem]) > 0:
# Check if elem in graph as there may be a case when there is no out edge from an airport
# In that case it won't be present as a key in graph
stack.append(graph[elem].pop())
else:
res.append(stack.pop())
# If there is no further children to traverse then add that airport to res
# This airport should be the last to go since we can't anywhere from this
# That's why we return the reverse of the result
return res[::-1]


def find_itinerary_using_hierholzers(tickets: List[List[str]]) -> List[str]:
flight_map: DefaultDict[str, List[str]] = defaultdict(list)
result: List[str] = []

# Populate the flight map with each departure and arrival
for departure, arrival in tickets:
flight_map[departure].append(arrival)

# Sort each list of destinations in reverse lexicographical order
for departure in flight_map:
flight_map[departure].sort(reverse=True)

def dfs_traversal(
current: str, flights: DefaultDict[str, List[str]], res: List[str]
):
destinations = flights[current]

# Traverse all destinations in the order of their lexicographical sorting
while destinations:
# Pop the last destination from the list (smallest lexicographical order due to reverse sorting)
next_destination = destinations.pop()
# Recursively perform DFS on the next destination
dfs_traversal(next_destination, flights, res)

# Append the current airport to the result after all destinations are visited
res.append(current)

dfs_traversal("JFK", flight_map, result)

return result[::-1]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading