0% completed
Problem Statement
You are given an m x n
binary matrix grid where each cell is either empty (0) or has an obstacle (1). You can move up, down, left, or right, but only through empty cells. You can remove up to k
obstacles along the way.
Return the shortest path from the top-left
corner (0, 0) to the bottom-right
corner (m - 1, n - 1). If it's not possible to reach the end, return -1.
Examples
Example 1
- Input: k = 1, grid =
[[0, 1, 0, 0],
[1, 1, 0, 1],
[0, 0, 0, 0],
[0, 1, 1, 0]]
-
Expected Output:
6
-
Justification: You can remove one obstacle to create a path from (0, 0) to (3, 3). The path can be (0,0) → (0,1) → (0,2) → (1,2) → (2,2) → (2,3) → (3,3). Here, we have removed the obstacle from (0, 1) position.
Example 2
- Input: k = 2, grid =
[[0, 0, 0],
[1, 1, 0],
[1, 1, 0],
[0, 0, 0]]
- Expected Output:
5
- Justification: You can remove two obstacles to create a path from (0, 0) to (3, 2). The path can be (0,0) → (0,1) → (0,2) → (1,2) → (2,2) -> (3, 2). Here, we don't need to remove any obstacles.
Example 3
- Input: k = 3, grid =
[[0, 0, 1],
[0, 1, 0],
[1, 0, 0],
[0, 1, 0]]
-
Expected Output:
5
-
Justification: You can remove three obstacles to create a path from (0, 0) to (3, 2). The path can be (0,0) → (0,1) → (0,2) → (1,2) → (2,2) → (3,2). Here, we have removed the obstacle from (0, 2) position.
Constraints:
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 40
- 1 <= k <= m * n
- grid[i][j] is either 0 or 1.
- grid[0][0] == grid[m - 1][n - 1] == 0
Solution
To solve this problem, we use a modified Breadth-First Search (BFS) approach. This method is efficient for finding the shortest path in an unweighted grid. By keeping track of the number of obstacles we can remove, we ensure that we explore all potential paths. This approach works well because BFS explores all nodes at the present depth level before moving on to nodes at the next depth level, ensuring the shortest path is found.
The key idea is to use a queue to store the current position, the number of steps taken, and the number of obstacles that can still be removed. We also use a set to keep track of visited positions along with the remaining obstacle removal quota to prevent redundant exploration.
Step-by-step Algorithm
-
Initialization:
- Create a queue to store the state as a tuple
(row, col, steps, remaining_k)
, representing the current position, steps taken so far, and remaining obstacle removals. - Initialize the queue with the starting point
(0, 0, 0, k)
(top-left corner with 0 steps taken and k obstacle removals). - Create a 3D boolean array
visited
of sizem x n x (k + 1)
to keep track of visited states, wherevisited[row][col][remaining_k]
is true if the state(row, col, remaining_k)
has been visited. - Mark the starting point as visited:
visited[0][0][k] = true
.
- Create a queue to store the state as a tuple
-
BFS Loop:
- While the queue is not empty:
- Dequeue the front element to get the current state
(x, y, steps, remaining_k)
. - Check if the current position
(x, y)
is the bottom-right corner(m-1, n-1)
. If yes, return the number of steps. - For each of the four possible movements (up, down, left, right):
- Calculate the new position
(new_x, new_y)
. - Check if the new position is within grid bounds.
- Calculate the new remaining obstacle removals:
new_k = remaining_k - grid[new_x][new_y]
. - If
new_k
is non-negative and the state(new_x, new_y, new_k)
has not been visited:- Mark the new state as visited:
visited[new_x][new_y][new_k] = true
. - Enqueue the new state
(new_x, new_y, steps + 1, new_k)
.
- Mark the new state as visited:
- Calculate the new position
- Dequeue the front element to get the current state
- While the queue is not empty:
-
End Condition:
- If the queue is empty and the bottom-right corner is not reached, return
-1
.
- If the queue is empty and the bottom-right corner is not reached, return
Algorithm Walkthrough
For the given input k = 3
, grid =
[[0, 0, 1],
[0, 1, 0],
[1, 0, 0],
[0, 1, 0]]
-
Initialization:
queue = deque([(0, 0, 0, 3)])
visited = [[[False] * 4 for _ in range(3)] for _ in range(4)]
visited[0][0][3] = True
-
BFS Loop:
-
Iteration 1:
- Dequeue
(0, 0, 0, 3)
, explore its neighbors:- Move to
(0, 1)
(right): new state(0, 1, 1, 3)
, mark visited and enqueue. - Move to
(1, 0)
(down): new state(1, 0, 1, 3)
, mark visited and enqueue. - No valid move to
(0, -1)
(left) or(-1, 0)
(up).
- Move to
queue = deque([(0, 1, 1, 3), (1, 0, 1, 3)])
- Dequeue
-
Iteration 2:
- Dequeue
(0, 1, 1, 3)
, explore its neighbors:- Move to
(0, 2)
(right): new state(0, 2, 2, 2)
(obstacle removed), mark visited and enqueue. - Move to
(1, 1)
(down): new state(1, 1, 2, 2)
(obstacle removed), mark visited and enqueue. - No valid move to
(0, 0)
(left) (already visited) or(-1, 1)
(up).
- Move to
queue = deque([(1, 0, 1, 3), (0, 2, 2, 2), (1, 1, 2, 2)])
- Dequeue
-
Iteration 3:
- Dequeue
(1, 0, 1, 3)
, explore its neighbors:- Move to
(1, 1)
(right): new state(1, 1, 2, 2)
(already visited). - Move to
(2, 0)
(down): new state(2, 0, 2, 2)
(obstacle removed), mark visited and enqueue. - Move to
(1, -1)
(left): invalid move (out of bounds). - Move to
(0, 0)
(up): new state(0, 0, 2, 3)
(already visited).
- Move to
queue = deque([(0, 2, 2, 2), (1, 1, 2, 2), (2, 0, 2, 2)])
- Dequeue
-
Iteration 4:
- Dequeue
(0, 2, 2, 2)
, explore its neighbors:- Move to
(0, 3)
(right): invalid move (out of bounds). - Move to
(1, 2)
(down): new state(1, 2, 3, 2)
, mark visited and enqueue. - Move to
(0, 1)
(left): new state(0, 1, 3, 2)
(already visited). - Move to
(-1, 2)
(up): invalid move (out of bounds).
- Move to
queue = deque([(1, 1, 2, 2), (2, 0, 2, 2), (1, 2, 3, 2)])
- Dequeue
-
Iteration 5:
- Dequeue
(1, 1, 2, 2)
, explore its neighbors:- Move to
(1, 2)
(right): new state(1, 2, 3, 2)
(already visited). - Move to
(2, 1)
(down): new state(2, 1, 3, 2)
, mark visited and enqueue. - Move to
(1, 0)
(left): new state(1, 0, 3, 2)
(already visited). - Move to
(0, 1)
(up): new state(0, 1, 3, 2)
(already visited).
- Move to
queue = deque([(2, 0, 2, 2), (1, 2, 3, 2), (2, 1, 3, 2)])
- Dequeue
-
Iteration 6:
- Dequeue
(2, 0, 2, 2)
, explore its neighbors:- Move to
(2, 1)
(right): new state(2, 1, 3, 2)
(already visited). - Move to
(3, 0)
(down): new state(3, 0, 3, 2)
, mark visited and enqueue. - Move to
(2, -1)
(left): invalid move (out of bounds). - Move to
(1, 0)
(up): new state(1, 0, 3, 2)
(already visited).
- Move to
queue = deque([(1, 2, 3, 2), (2, 1, 3, 2), (3, 0, 3, 2)])
- Dequeue
-
Iteration 7:
- Dequeue
(1, 2, 3, 2)
, explore its neighbors:- Move to
(1, 3)
(right): invalid move (out of bounds). - Move to
(2, 2)
(down): new state(2, 2, 4, 2)
, mark visited and enqueue. - Move to
(1, 1)
(left): new state(1, 1, 4, 2)
(already visited). - Move to
(0, 2)
(up): new state(0, 2, 4, 2)
(already visited).
- Move to
queue = deque([(2, 1, 3, 2), (3, 0, 3, 2), (2, 2, 4, 2)])
- Dequeue
-
Iteration 8:
- Dequeue
(2, 1, 3, 2)
, explore its neighbors:- Move to
(2, 2)
(right): new state(2, 2, 4, 2)
(already visited). - Move to
(3, 1)
(down): new state(3, 1, 4, 1)
(obstacle removed), mark visited and enqueue. - Move to
(2, 0)
(left): new state(2, 0, 4, 2)
(already visited). - Move to
(1, 1)
(up): new state(1, 1, 4, 2)
(already visited).
- Move to
queue = deque([(3, 0, 3, 2), (2, 2, 4, 2), (3, 1, 4, 1)])
- Dequeue
-
Iteration 9:
- Dequeue
(3, 0, 3, 2)
, explore its neighbors:- Move to
(3, 1)
(right): new state(3, 1, 4, 1)
(already visited). - Move to
(4, 0)
(down): invalid move (out of bounds). - Move to
(3, -1)
(left): invalid move (out of bounds). - Move to
(2, 0)
(up): new state(2, 0, 4, 2)
(already visited).
- Move to
queue = deque([(2, 2, 4, 2), (3, 1, 4, 1)])
- Dequeue
-
Iteration 10:
- Dequeue
(2, 2, 4, 2)
, explore its neighbors:- Move to
(2, 3)
(right): invalid move (out of bounds). - Move to
(3, 2)
(down): new state(3, 2, 5, 2)
, mark visited and enqueue. - Move to
(2, 1)
(left): new state(2, 1, 5, 2)
(already visited). - Move to
(1, 2)
(up): new state(1, 2, 5, 2)
(already visited).
- Move to
queue = deque([(3, 1, 4, 1), (3, 2, 5, 2)])
- Dequeue
-
Iteration 11:
- Dequeue
(3, 1, 4, 1)
, explore its neighbors:- Move to
(3, 2)
(right): new state(3, 2, 5, 1)
(already visited). - Move to
(4, 1)
(down): invalid move (out of bounds). - Move to
(3, 0)
(left): new state(3, 0, 5, 1)
(already visited). - Move to
(2, 1)
(up): new state(2, 1, 5, 1)
(already visited).
- Move to
queue = deque([(3, 2, 5, 2)])
- Dequeue
-
Iteration 12:
- Dequeue
(3, 2, 5, 2)
, explore its neighbors:- Move to
(3, 3)
(right): invalid move (out of bounds). - Move to
(4, 2)
(down): invalid move (out of bounds). - Move to
(3, 1)
(left): new state(3, 1, 6, 2)
(already visited). - Move to
(2, 2)
(up): new state(2, 2, 6, 2)
(already visited).
- Move to
queue = deque([])
- Dequeue
-
Since the queue is empty and the bottom-right corner is not reached, return
-1
.
-
-
End Condition:
- The bottom-right corner
(3, 2)
is reached in5
steps with2
obstacles removed.
- The bottom-right corner
So, the expected output is 5
.
Code
Complexity Analysis
Time Complexity
- In the worst-case scenario, we might explore every cell in the grid for every possible value of remaining k (from k down to 0).
- This results in a time complexity of O(m * n * k), where m is the number of rows and n is the number of columns in the grid.
- Each cell can be visited in up to k different states (each state representing a different number of obstacles that can still be removed).
Space Complexity
- The space complexity is also O(m * n * k) due to the visited array, which keeps track of the state of each cell for each possible value of remaining k.
- Additionally, the queue used for BFS can hold up to m * n * k elements in the worst case.
.....
.....
.....
Table of Contents
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible