3341. Find Minimum Time to Reach Last Room I
Problem Description
You are in a dungeon structured as a grid consisting of n
rows and m
columns. You must navigate through this grid to reach your destination. The grid is represented by a 2D array moveTime
, where each cell moveTime[i][j]
specifies the earliest possible time in seconds that you can start moving into that room (i, j)
. You begin your journey from the top-left corner of the grid, room (0, 0)
, at time t = 0
. Each move from one room to an adjacent room (horizontally or vertically) takes exactly one second. Your objective is to find the minimum time necessary to reach the bottom-right corner of the grid, room (n - 1, m - 1)
.
Intuition
To solve this problem, we can utilize Dijkstra's algorithm, which is well-suited for finding the shortest path in graphs with non-negative weights. Here, the grid can be visualized as a weighted graph where each cell is a node and each move from one cell to an adjacent cell represents an edge with a weight of one second.
-
Set Up the Distance Array: Initialize a 2D array
dist
to keep track of the minimum time to reach each room. Set the initial room’s(0, 0)
time to0
and all others to infinity (inf
), as we're interested in the shortest time. -
Priority Queue for Exploration: Use a priority queue to explore the rooms. We start by pushing the starting room
(0, 0)
with a time of0
into the priority queue. -
Explore Using Dijkstra's Approach: With the priority queue, iteratively expand the least-time path available. For the current room
(i, j)
, look at its adjacent rooms. Calculate the time it would take to get to an adjacent room, which is the maximum of themoveTime
requirement and the accumulated time so far plus one second for the move. -
Update Times and Continue: If moving to the adjacent room
(x, y)
can be done in a time earlier than what’s currently recorded indist[x][y]
, update it and push this new state into the priority queue. -
Terminate on Goal Room: When reaching the target room
(n-1, m-1)
with the least time, return this time as it's the shortest time needed given the constraints of themoveTime
grid.
This intuitive approach leverages both the spatial nature of grid movement and the temporal constraints provided, efficiently finding the optimal route in terms of time.
Learn more about Graph, Shortest Path and Heap (Priority Queue) patterns.
Solution Approach
The problem of finding the minimum time to navigate the dungeon grid while adhering to the constraints of the moveTime
array can be efficiently addressed using Dijkstra's Algorithm. This algorithm is effective in determining the shortest path in a graph, especially when dealing with non-negative edge weights, as is the case here where each move takes one second.
Step-by-Step Implementation:
-
Initialize the Distance Array:
- Create a 2D array
dist
of the same dimensions asmoveTime
which will hold the minimum time required to reach each room. Initially, set every room's time to infinity (inf
), except for the starting room(0, 0)
, which is set to0
since you start there at time0
.
dist = [[inf] * m for _ in range(n)] dist[0][0] = 0
- Create a 2D array
-
Set Up the Priority Queue:
- Use a priority queue to facilitate the exploration of the least-time path first. This priority queue will store tuples in the format
(d, i, j)
, whered
is the time to reach the room(i, j)
. Begin by inserting the starting room with its initial time into the queue.
pq = [(0, 0, 0)]
- Use a priority queue to facilitate the exploration of the least-time path first. This priority queue will store tuples in the format
-
Dijkstra's Exploration:
- While the priority queue is not empty, extract the room
(i, j)
with the current minimum timed
. - If this room is the destination
(n - 1, m - 1)
, returnd
as the shortest time. - Skip this room if the current time
d
exceedsdist[i][j]
, as this indicates a previously discovered shorter path. - For each of the four possible adjacent rooms (up, down, left, right), calculate the potential time
t
to move there, which is the maximum of the move's time constraint and the accumulated time plus one second.
- While the priority queue is not empty, extract the room
-
Update and Push New States:
- If the new calculated time
t
to an adjacent room(x, y)
is less than the currently recorded time indist[x][y]
, updatedist[x][y]
and push the new state(t, x, y)
into the priority queue for further exploration.
for a, b in pairwise(dirs): x, y = i + a, j + b if 0 <= x < n and 0 <= y < m: t = max(moveTime[x][y], dist[i][j]) + 1 if dist[x][y] > t: dist[x][y] = t heappush(pq, (t, x, y))
- If the new calculated time
-
Termination:
- The algorithm naturally terminates as soon as the destination is dequeued from the priority queue with the least possible time path due to Dijkstra's greedy nature, ensuring optimality.
This structured approach efficiently navigates the grid using the properties of the priority queue to always process the most promising path next, thus ensuring that the solution is optimal in terms of the time constraints imposed.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution with a small example. Assume the moveTime
grid is as follows:
moveTime = [ [0, 2, 2], [2, 3, 5], [5, 5, 8] ]
This grid has 3 rows and 3 columns. We need to determine the minimum time needed to move from the top-left corner (0, 0)
to the bottom-right corner (2, 2)
.
Step-by-Step Walkthrough:
-
Initialize the Distance Array:
We initialize
dist
as:dist = [ [0, inf, inf], [inf, inf, inf], [inf, inf, inf] ]
The starting room
(0, 0)
is set to0
. -
Set Up the Priority Queue:
Start with a priority queue containing the starting point:
pq = [(0, 0, 0)] # (time, row, col)
-
Dijkstra's Exploration:
Process rooms by expanding from the least-time path:
-
Step 1: Dequeue
(0, 0, 0)
from the queue.- Explore adjacent rooms
(0, 1)
and(1, 0)
. - To
(0, 1)
: Time =max(2, 0) + 1 = 3
. Updatedist[0][1] = 3
and add(3, 0, 1)
to the queue. - To
(1, 0)
: Time =max(2, 0) + 1 = 3
. Updatedist[1][0] = 3
and add(3, 1, 0)
to the queue.
- Explore adjacent rooms
-
Step 2: Dequeue
(3, 0, 1)
(because 3 is the smallest).- Explore
(0, 2)
and(1, 1)
. - To
(0, 2)
: Time =max(2, 3) + 1 = 4
. Updatedist[0][2] = 4
and add(4, 0, 2)
to the queue. - To
(1, 1)
: Time =max(3, 3) + 1 = 4
. Updatedist[1][1] = 4
and add(4, 1, 1)
to the queue.
- Explore
-
Step 3: Dequeue
(3, 1, 0)
(next smallest time).- Explore
(2, 0)
and(1, 1)
. - To
(2, 0)
: Time =max(5, 3) + 1 = 6
. Updatedist[2][0] = 6
and add(6, 2, 0)
to the queue.
- Explore
-
Step 4: Dequeue
(4, 0, 2)
.- Explore
(1, 2)
. - To
(1, 2)
: Time =max(5, 4) + 1 = 6
. Updatedist[1][2] = 6
and add(6, 1, 2)
to the queue.
- Explore
-
-
Continue Exploration:
- Follow a similar process until you reach
(2, 2)
:- From
(4, 1, 1)
, explore to(2, 1)
, and so on. - Eventually, reach
(2, 2)
with time7
.
- From
- Follow a similar process until you reach
-
Termination:
When
(2, 2)
is reached, the queue provides the minimum time8
. Thus, the minimum time required to navigate from(0, 0)
to(2, 2)
respecting themoveTime
constraints is8
.
This walkthrough showcases the step-by-step application of Dijkstra's algorithm in traversing the grid based on the constraints provided by moveTime
.
Solution Implementation
1from typing import List
2from heapq import heappop, heappush
3from itertools import pairwise
4from math import inf
5
6class Solution:
7 def minTimeToReach(self, moveTime: List[List[int]]) -> int:
8 n, m = len(moveTime), len(moveTime[0]) # Get the dimensions of the grid
9
10 # Initialize distance grid with infinity
11 dist = [[inf] * m for _ in range(n)]
12 dist[0][0] = 0 # Starting point has 0 distance
13
14 # Priority queue for Dijkstra's algorithm starting at top-left corner
15 pq = [(0, 0, 0)]
16
17 # Directions for moving up, down, left, and right
18 dirs = (-1, 0, 1, 0, -1)
19
20 while True:
21 d, i, j = heappop(pq) # Pop the smallest distance node
22
23 # If we've reached the bottom-right corner, return the total distance
24 if i == n - 1 and j == m - 1:
25 return d
26
27 # Skip if we've found a shorter path to (i, j) already
28 if d > dist[i][j]:
29 continue
30
31 # Explore all 4 possible directions
32 for a, b in pairwise(dirs):
33 x, y = i + a, j + b
34 # Check if the new position is within bounds
35 if 0 <= x < n and 0 <= y < m:
36 # Calculate the time to reach this new position
37 t = max(moveTime[x][y], dist[i][j]) + 1
38 # If found a shorter path to (x, y), update distance and push to queue
39 if dist[x][y] > t:
40 dist[x][y] = t
41 heappush(pq, (t, x, y))
42
1import java.util.Arrays;
2import java.util.PriorityQueue;
3
4class Solution {
5 public int minTimeToReach(int[][] moveTime) {
6 // Get the number of rows (n) and columns (m) from the moveTime matrix
7 int n = moveTime.length;
8 int m = moveTime[0].length;
9
10 // Initialize a distance matrix filled with maximum integer values
11 int[][] distance = new int[n][m];
12 for (int[] row : distance) {
13 Arrays.fill(row, Integer.MAX_VALUE);
14 }
15
16 // Starting point (0, 0) has a distance of 0
17 distance[0][0] = 0;
18
19 // Priority queue to store cells in the form [current_distance, row, column]
20 // Sorted by current_distance
21 PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a, b) -> a[0] - b[0]);
22 priorityQueue.offer(new int[] {0, 0, 0});
23
24 // Directions arrays for moving up, down, left, and right
25 int[] directions = {-1, 0, 1, 0, -1};
26
27 // Process until we find the minimum distance to the bottom-right corner
28 while (true) {
29 // Get the element with the smallest distance
30 int[] current = priorityQueue.poll();
31 int currentDistance = current[0];
32 int i = current[1];
33 int j = current[2];
34
35 // Check if we've reached the bottom-right corner
36 if (i == n - 1 && j == m - 1) {
37 return currentDistance;
38 }
39
40 // If there is already a better distance, continue
41 if (currentDistance > distance[i][j]) {
42 continue;
43 }
44
45 // Explore all four possible directions (up, down, left, right)
46 for (int k = 0; k < 4; k++) {
47 int x = i + directions[k];
48 int y = j + directions[k + 1];
49
50 // Check if new position is within bounds
51 if (x >= 0 && x < n && y >= 0 && y < m) {
52 // Calculate new time distance for this position
53 int newTime = Math.max(moveTime[x][y], distance[i][j]) + 1;
54
55 // If this path offers a shorter distance, update and enqueue
56 if (distance[x][y] > newTime) {
57 distance[x][y] = newTime;
58 priorityQueue.offer(new int[] {newTime, x, y});
59 }
60 }
61 }
62 }
63 }
64}
65
1#include <vector>
2#include <queue>
3#include <array>
4#include <climits>
5
6class Solution {
7public:
8 int minTimeToReach(std::vector<std::vector<int>>& move_time) {
9 int n = move_time.size(); // Number of rows
10 int m = move_time[0].size(); // Number of columns
11
12 // Distance matrix to store minimum time to reach each cell
13 std::vector<std::vector<int>> dist(n, std::vector<int>(m, INT_MAX));
14 dist[0][0] = 0; // Starting point with 0 time
15
16 // Priority queue to process cells in increasing order of time
17 std::priority_queue<std::array<int, 3>, std::vector<std::array<int, 3>>, std::greater<>> pq;
18 pq.push({0, 0, 0}); // Push starting cell with time 0
19
20 int directions[5] = {-1, 0, 1, 0, -1}; // Movement directions for up, down, left, and right
21
22 while (!pq.empty()) {
23 auto [d, i, j] = pq.top(); // Get the cell with the smallest time
24 pq.pop();
25
26 // If we have reached the bottom-right corner, return the time
27 if (i == n - 1 && j == m - 1) {
28 return d;
29 }
30
31 // If this path doesn't improve the time for the cell, skip it
32 if (d > dist[i][j]) {
33 continue;
34 }
35
36 // Check all 4 possible directions
37 for (int dir = 0; dir < 4; ++dir) {
38 int x = i + directions[dir];
39 int y = j + directions[dir + 1];
40
41 // If the neighboring cell is within bounds
42 if (x >= 0 && x < n && y >= 0 && y < m) {
43 // Calculate the time to move to the neighbor cell
44 int new_time = std::max(move_time[x][y], dist[i][j]) + 1;
45
46 // If the new time is better, update the distance and push to queue
47 if (dist[x][y] > new_time) {
48 dist[x][y] = new_time;
49 pq.push({new_time, x, y});
50 }
51 }
52 }
53 }
54 // This point should not be reached due to the condition of reaching the destination
55 return -1; // Returning -1 to indicate an unexpected case
56 }
57};
58
1// Define the main function to calculate the minimum time to reach the bottom-right corner of the grid
2function minTimeToReach(moveTime: number[][]): number {
3 const [n, m] = [moveTime.length, moveTime[0].length]; // Get dimensions of the grid
4 const dist: number[][] = Array.from({ length: n }, () => Array(m).fill(Infinity)); // Create a distance matrix initialized to Infinity
5 dist[0][0] = 0; // Start at the top-left corner with a distance of 0
6
7 // Priority queue to keep track of cells to visit, sorted by the distance
8 const pq = new PriorityQueue({ compare: (a, b) => a[0] - b[0] });
9 pq.enqueue([0, 0, 0]); // Enqueue the starting position with a distance of 0
10
11 // Directions for moving in the grid (up, right, down, left)
12 const dirs = [-1, 0, 1, 0, -1];
13
14 // Loop until the priority queue is empty or the bottom-right corner is reached
15 while (true) {
16 const [d, i, j] = pq.dequeue(); // Dequeue the cell with the smallest distance
17
18 // Check if we have reached the bottom-right corner
19 if (i === n - 1 && j === m - 1) {
20 return d; // Return the distance for reaching the bottom-right corner
21 }
22
23 // Skip this position if a shorter path has been found
24 if (d > dist[i][j]) {
25 continue; // Continue with the next iteration if the current distance is already better
26 }
27
28 // Explore adjacent cells
29 for (let k = 0; k < 4; ++k) {
30 const [x, y] = [i + dirs[k], j + dirs[k + 1]]; // Calculate new cell coordinates
31 // Check if the new coordinates are within the grid boundaries
32 if (x >= 0 && x < n && y >= 0 && y < m) {
33 // Calculate the new distance considering the current time
34 const t = Math.max(moveTime[x][y], dist[i][j]) + 1;
35 // If a shorter distance is found, update and enqueue the new position
36 if (dist[x][y] > t) {
37 dist[x][y] = t; // Update distance matrix
38 pq.enqueue([t, x, y]); // Enqueue the new position with updated distance
39 }
40 }
41 }
42 }
43}
44
Time and Space Complexity
The time complexity of the code is O(n × m × log(n × m))
. This arises from the fact that the algorithm uses Dijkstra's algorithm with a priority queue (min-heap). Pushing and popping from the heap costs O(log k)
where k
is the number of elements in the heap, and k
can reach up to n × m
in the worst case, where n
is the number of rows and m
is the number of columns. For each cell, the algorithm considers up to four neighboring cells, leading to O(n × m × log(n × m))
in total.
The space complexity of the code is O(n × m)
. This is due to the storage of the dist
array that keeps track of the minimum time to reach each cell, and the priority queue pq
that can store up to n × m
elements.
Learn more about how to find time and space complexity quickly.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could
Shortest Path Between A and B Prereq BFS on Graph problems graph_bfs Given an unweighted connected graph return the length of the shortest path between two nodes A and B in terms of the number of edges Assume there always exists a path between nodes A and B Input graph
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!