Grokking 75: Top Coding Interview Questions
Ask Author
Back to course home

0% completed

Vote For New Content
Solution: Shortest Path in a Grid with Obstacles Elimination
Table of Contents

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

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

  1. 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 size m x n x (k + 1) to keep track of visited states, where visited[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.
  2. BFS Loop:

    • While the queue is not empty:
      1. Dequeue the front element to get the current state (x, y, steps, remaining_k).
      2. Check if the current position (x, y) is the bottom-right corner (m-1, n-1). If yes, return the number of steps.
      3. 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).
  3. End Condition:

    • If the queue is empty and the bottom-right corner is not reached, return -1.

Algorithm Walkthrough

For the given input k = 3, grid =

 [[0, 0, 1],
  [0, 1, 0],
  [1, 0, 0],
  [0, 1, 0]]
 
  1. Initialization:

    • queue = deque([(0, 0, 0, 3)])
    • visited = [[[False] * 4 for _ in range(3)] for _ in range(4)]
    • visited[0][0][3] = True
  2. 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).
      • queue = deque([(0, 1, 1, 3), (1, 0, 1, 3)])
    • 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).
      • queue = deque([(1, 0, 1, 3), (0, 2, 2, 2), (1, 1, 2, 2)])
    • 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).
      • queue = deque([(0, 2, 2, 2), (1, 1, 2, 2), (2, 0, 2, 2)])
    • 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).
      • queue = deque([(1, 1, 2, 2), (2, 0, 2, 2), (1, 2, 3, 2)])
    • 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).
      • queue = deque([(2, 0, 2, 2), (1, 2, 3, 2), (2, 1, 3, 2)])
    • 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).
      • queue = deque([(1, 2, 3, 2), (2, 1, 3, 2), (3, 0, 3, 2)])
    • 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).
      • queue = deque([(2, 1, 3, 2), (3, 0, 3, 2), (2, 2, 4, 2)])
    • 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).
      • queue = deque([(3, 0, 3, 2), (2, 2, 4, 2), (3, 1, 4, 1)])
    • 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).
      • queue = deque([(2, 2, 4, 2), (3, 1, 4, 1)])
    • 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).
      • queue = deque([(3, 1, 4, 1), (3, 2, 5, 2)])
    • 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).
      • queue = deque([(3, 2, 5, 2)])
    • 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).
      • queue = deque([])
    • Since the queue is empty and the bottom-right corner is not reached, return -1.

  3. End Condition:

    • The bottom-right corner (3, 2) is reached in 5 steps with 2 obstacles removed.

So, the expected output is 5.

Code

Python3
Python3

. . . .

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.

.....

.....

.....

Like the course? Get enrolled and start learning!

Table of Contents

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible