|
1 | 1 | # [Problem 2045: Second Minimum Time to Reach Destination](https://leetcode.com/problems/second-minimum-time-to-reach-destination/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- This is another graph problem (sort of a shortest path problem, but with a few differences) |
| 5 | +- There are a few twists in this problem: |
| 6 | + - The red/green traffic signals are going to change how we factor in edge lengths. We'll have to come up with a way of handling this, but essentially as we traverse a path we need to use the traffic rules to figure out how long it will take. |
| 7 | + - Finding the "second minimum time" is a little trickier than finding the (strict) minimum time. The standard shortest path algorithms would do something like replacing (in the adjacency matrix) the distances between two vertices if a new path had a shorter distance. But now I think we need to maintain *two* adjacency matrices: one for the minimum distances, and one for the second minimum distances (i.e., the minimum values that are strictly greater than whatever is in the minimum distance matrix. |
| 8 | +- Another important part of the problem is that we don't need to compute the paths between *every* pair of vertices, since we know that the only path that matters is from vertex 1 to vertex $n$. So instead of the Floyd-Warshall algorithm, which we used for [yesterday's problem](https://github.com/ContextLab/leetcode-solutions/blob/main/problems/2976/jeremymanning.md) and the [day before yesterday's problem](https://github.com/ContextLab/leetcode-solutions/blob/main/problems/1334/jeremymanning.md), I think we should use Dijkstra's algorithm for this problem (but modified as described above). Pasting in from yesterday's notes, here is the pseudocode for Dijkstra's algorithm: |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +- Ok...so now we have a general approach; let's figure out how to deal with these twists. |
4 | 13 |
|
5 | 14 | ## Refining the problem, round 2 thoughts
|
| 15 | +- Handling traffic rules: |
| 16 | + - We know that all signals start off as green at time 0 |
| 17 | + - Let's say that the current signal status when we reach vertex `i` is `isGreen`, and the journey from vertex `i` to `j` takes `x` minutes. If `isGreen == True` and `x` is between 0 and `change` minutes, the status doesn't change (but now we only have `change - x` minutes before the next transition). Alternatively, if `change < x <= 2 * change`, then now `isGreen == False` and we'll need to add `y = 2 * change - x` minutes to the journey from `j` to the next destination. (If `j` is the endpoint-- i.e., vertex $n$, then we don't need to pay that extra cost.) |
| 18 | + - If `isGreen == False` then we need to wait until the signal turns green (this takes `y` minutes, as computed above) and then we proceed as though `isGreen == True`. |
| 19 | + - Only the final (up to) `2 * change` minutes of the journey matter with respect to accounting for traffic rules. I think we can compute the cost as something like `x + extra`, where `extra` is computed as follows: |
| 20 | + - Start with `extra = 0` |
| 21 | + - If there is time remaining until the signal turns green from the remaining journey (let's call that amount `y`), then `extra += y`. For the first journey (from vertex 1 to a neighbor) we know that `y == 0`. |
| 22 | + - Then take `remainder = x % (2 * change)`: |
| 23 | + - If `0 <= remainder < change` then keep track of how much less time we have on the *next* journey-- but we can leave the next vertex right away |
| 24 | + - If `change <= remainder < 2 * change` then we arrive when the signal is red, so on the next journey from the destination we'll need to wait `2 * change - remainder` minutes to leave. |
| 25 | + - Ok...so these notes are a little convoluted. But what I think I'm coming to is that we're going to need to keep track of `greenTimeSpent` (amount of time spent traveling to the current vertex during the most recent green signal-- *if the signal is green when we arrive*; if we arrive when the signal is red, `greenTimeSpent = 0`). And we also need to keep track of `timeUntilGreen`-- the amount of time left until we can leave the destination vertex. But `timeUntilGreen` only applies if we arrive when the signal is red; otherwise (if the signal is green), then we set `timeUntilGreen = 0`. Then, when we're computing travel times between vertices, we want to add `timeUntilGreen` to the stated travel time. And then we *subtract* `greenTimeSpent` when we need figure out the signal status upon arrival at the destination vertex. |
| 26 | +- Finding the second minimum time: |
| 27 | + - In the "standard" Dijkstra's algorithm, we continually replace the path distance from `i` to `j` with alternative smaller values if/when we find them. But in the "second minimum" version, we'll need to maintain two parallel representations of the path distances: |
| 28 | + - The first representation is the standard minimum path distance |
| 29 | + - The second representation (which stores the second minimums) replaces the path distance from `i` to `j` with an alternative smaller value only if (a) it's smaller than the current distance in the second minimum representation *and* it's strictly greater than whatever the minimum path distance from `i` to `j` is. |
| 30 | +- Functions to write: |
| 31 | + - `computeTravelTime(current_time, travel_time)`: returns the time needed to get to the next vertex, accounting for signal status |
| 32 | + - Actually...`travel_time` is always the same, so we can just use `computeTravelTime(current_time)`! |
| 33 | + - Then we just need to implement this modified version of Dijkstra's algorithm (i.e., breadth first search) to find the second minimum time needed to get from vertex 1 to $n$. |
| 34 | +- One potential edge case: what if there is only 1 path from vertex 1 to $n$? Then I think to get the second minimum time, we would need to double back along the trip between the nearest vertices (accounting for signal status)-- e.g., we'd need to add an extra loop (for some adjacent vertices `i` and `j`): `... --> i --> j --> i --> j --> ...`. |
6 | 35 |
|
7 | 36 | ## Attempted solution(s)
|
8 | 37 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 38 | +class Solution: |
| 39 | + def secondMinimum(self, n: int, edges: List[List[int]], time: int, change: int) -> int: |
| 40 | + def computeTravelTime(current_time): |
| 41 | + # Calculate effective travel time considering traffic lights |
| 42 | + cycle = 2 * change |
| 43 | + if (current_time // change) % 2 == 1: # lights are red |
| 44 | + wait_time = cycle - (current_time % cycle) |
| 45 | + return current_time + wait_time + time |
| 46 | + else: # lights are green |
| 47 | + return current_time + time |
| 48 | + |
| 49 | + # Build the graph |
| 50 | + graph = [[] for _ in range(n + 1)] |
| 51 | + for u, v in edges: |
| 52 | + graph[u].append(v) |
| 53 | + graph[v].append(u) |
| 54 | + |
| 55 | + # List to keep track of the minimum and second minimum times to each vertex |
| 56 | + min_times = [[float('inf'), float('inf')] for _ in range(n + 1)] |
| 57 | + min_times[1][0] = 0 |
| 58 | + |
| 59 | + # Queue for BFS |
| 60 | + queue = [(0, 1)] # (current time, current vertex) |
| 61 | + |
| 62 | + while queue: |
| 63 | + curr_time, vertex = queue.pop(0) |
| 64 | + |
| 65 | + # Explore neighbors |
| 66 | + for neighbor in graph[vertex]: |
| 67 | + next_time = computeTravelTime(curr_time) |
| 68 | + |
| 69 | + if next_time < min_times[neighbor][0]: |
| 70 | + min_times[neighbor][1] = min_times[neighbor][0] |
| 71 | + min_times[neighbor][0] = next_time |
| 72 | + queue.append((next_time, neighbor)) |
| 73 | + elif min_times[neighbor][0] < next_time < min_times[neighbor][1]: |
| 74 | + min_times[neighbor][1] = next_time |
| 75 | + queue.append((next_time, neighbor)) |
| 76 | + |
| 77 | + # Return the second minimum time to reach vertex n |
| 78 | + return min_times[n][1] |
11 | 79 | ```
|
| 80 | +- Given test cases pass |
| 81 | +- I'm out of time for thinking about this; submitting... |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | +😌 phew! |
| 86 | + |
0 commit comments