Problem
A competitive runner would like to create a route that starts and ends at his house, with the condition that the route goes entirely uphill at first, and then entirely downhill.
Given a dictionary of places of the form {location: elevation}
, and a dictionary mapping paths between some of these locations to their corresponding distances, find the length of the shortest route satisfying the condition above. Assume the runner’s home is location 0
.
Examples
Example 1:
Input: elevations = {0: 5, 1: 25, 2: 15, 3: 20, 4: 10}, paths = {
(0, 1): 10,
(0, 2): 8,
(0, 3): 15,
(1, 3): 12,
(2, 4): 10,
(3, 4): 5,
(3, 0): 17,
(4, 0): 10
}
Output: 28
Explanation: The shortest path is 0 -> 2 -> 4 -> 0.
Elevations during the run 5 -> 15 -> 10 -> 5, distance is:
0 -> 2 = 8, 2 -> 4 = 10, 4 -> 0 = 10 = 8 + 10 + 10 = 28.
Solution
Method 1 - DFS
Here’s an overall approach to solve the problem:
- Graph Representation:
- Use the
elevations
dictionary to store the elevation of each location. - Use the
paths
dictionary to represent edges between the locations with their distances.
- Use the
- Finding Valid Paths:
- Use Depth-First Search (DFS) to explore possible uphill routes from the home location
0
. - For each uphill path found, use DFS to search for a valid downhill path back to the home location
0
.
- Use Depth-First Search (DFS) to explore possible uphill routes from the home location
- Tracking the Shortest Path:
- Track the shortest valid path satisfying the constraints.
Code
Java
public class KnightsTourRoute {
private int shortestDistance = Integer.MAX_VALUE;
private List<Integer> shortestPath = new ArrayList<>();
public void computeShortestRoute(Map<Integer, Integer> elevations, Map < int[], Integer > paths) {
Set<Integer> visited = new HashSet<>();
visited.add(0);
List<Integer> path = new ArrayList<>();
path.add(0);
dfsUpHill(0, paths, elevations, visited, 0, path);
}
private void dfsUpHill(int current, Map < int[], Integer > paths, Map<Integer, Integer> elevations, Set<Integer> visited, int totalDistance, List<Integer> path) {
if (current != 0 && elevations.get(current) < elevations.get(0)) {
dfsDownHill(current, paths, elevations, visited, totalDistance, path);
return;
}
for (Map.Entry < int[], Integer > entry: paths.entrySet()) {
int start = entry.getKey()[0];
int end = entry.getKey()[1];
int distance = entry.getValue();
if (start == current && !visited.contains(end) && elevations.get(end) > elevations.get(current)) {
visited.add(end);
path.add(end);
dfsUpHill(end, paths, elevations, visited, totalDistance + distance, path);
path.remove(path.size() - 1);
visited.remove(end);
}
}
}
private static void dfsDownHill(int current, Map <int[], Integer> paths, Map<Integer, Integer> elevations, Set<Integer> visited, int totalDistance, List<Integer> path) {
if (current == 0) {
if (totalDistance < shortestDistance) {
shortestDistance = totalDistance;
shortestPath = new ArrayList<>(path);
}
return;
}
for (Map.Entry < int[], Integer > entry: paths.entrySet()) {
int start = entry.getKey()[0];
int end = entry.getKey()[1];
int distance = entry.getValue();
if (start == current && !visited.contains(end) && elevations.get(end) < elevations.get(current)) {
visited.add(end);
path.add(end);
dfsDownHill(end, paths, elevations, visited, totalDistance + distance, path);
path.remove(path.size() - 1);
visited.remove(end);
}
}
}
}
Python
def find_shortest_route(
elevations: dict[int, int], paths: dict[tuple[int, int], int]
) -> tuple[list[int], int]:
def dfs_uphill(
current: int, visited: set, path: list[int], total_dist: int
) -> None:
nonlocal shortest_path, shortest_distance
# Once we reach the downhill part, start checking downhill paths
if current != 0 and elevations[current] < elevations[home]:
dfs_downhill(current, visited, path, total_dist)
return
for (start, end), dist in paths.items():
if (
start == current
and end not in visited
and elevations[end] > elevations[current]
):
visited.add(end)
path.append(end)
dfs_uphill(end, visited, path, total_dist + dist)
path.pop()
visited.remove(end)
def dfs_downhill(
current: int, visited: set, path: list[int], total_dist: int
) -> None:
nonlocal shortest_path, shortest_distance
if current == home:
if total_dist < shortest_distance:
shortest_distance = total_dist
shortest_path = list(path)
return
for (start, end), dist in paths.items():
if (
start == current
and end not in visited
and elevations[end] < elevations[current]
):
visited.add(end)
path.append(end)
dfs_downhill(end, visited, path, total_dist + dist)
path.pop()
visited.remove(end)
home = 0
shortest_path: list[int] = []
shortest_distance: int = float("inf")
visited: set = set([home])
dfs_uphill(home, visited, [home], 0)
if shortest_path:
return shortest_path, shortest_distance
else:
return [], -1
Complexity
- ⏰ Time complexity:
O(E.V)
, whereE
is number of edges, andV
is number of vertices- In the worst case, the algorithm explores all possible paths from the home location. Given that there are
V
locations (vertices) andE
paths (edges), the DFS explores each edge multiple times. - For each node, we examine all edges, leading to a complexity component of
O(E)
.
- In the worst case, the algorithm explores all possible paths from the home location. Given that there are
- 🧺 Space complexity:
O(V)
, for using recursion stack and for usingvisited
set.