Knowledge Guide
HomeDSAGraphs

hard Shortest Path in a Grid with Obstacles Elimination

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

[[0, 1, 0, 0],
 [1, 1, 0, 1],
 [0, 0, 0, 0],
 [0, 1, 1, 0]]

Example 2

  [[0, 0, 0],
   [1, 1, 0],
   [1, 1, 0],
   [0, 0, 0]]

Example 3

 [[0, 0, 1],
  [0, 1, 0],
  [1, 0, 0],
  [0, 1, 0]]
 

Constraints:

Try it yourself

Try solving this question here:

✅ Solution Shortest Path in a Grid with Obstacles Elimination

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

java
import java.util.*;

class Solution {

  public int shortestPath(int[][] grid, int k) {
    int m = grid.length;
    int n = grid[0].length;

    // Initialize the queue for BFS
    Queue<int[]> queue = new LinkedList<>();
    // Add starting point with 0 steps and k obstacle removals left
    queue.offer(new int[] { 0, 0, 0, k });

    // 3D array to keep track of visited states [row][col][remaining k]
    boolean[][][] visited = new boolean[m][n][k + 1];
    // Mark the starting point as visited
    visited[0][0][k] = true;

    // Directions array for moving up, down, left, and right
    int[][] directions = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

    // BFS loop
    while (!queue.isEmpty()) {
      // Dequeue the front element
      int[] current = queue.poll();
      int x = current[0];
      int y = current[1];
      int steps = current[2];
      int remainingK = current[3];

      // Check if we have reached the bottom-right corner
      if (x == m - 1 && y == n - 1) {
        return steps;
      }

      // Explore all four possible directions
      for (int[] dir : directions) {
        int newX = x + dir[0];
        int newY = y + dir[1];

        // Check if new position is within grid bounds
        if (newX >= 0 && newX < m && newY >= 0 && newY < n) {
          // Calculate remaining k after moving to new cell
          int newK = remainingK - grid[newX][newY];

          // If the new state is valid and not visited
          if (newK >= 0 && !visited[newX][newY][newK]) {
            // Mark new state as visited
            visited[newX][newY][newK] = true;
            // Enqueue the new state
            queue.offer(new int[] { newX, newY, steps + 1, newK });
          }
        }
      }
    }

    // Return -1 if no path is found
    return -1;
  }

  public static void main(String[] args) {
    Solution sol = new Solution();
    int[][] grid1 = {
      { 0, 1, 0, 0 },
      { 1, 1, 0, 1 },
      { 0, 0, 0, 0 },
      { 0, 1, 1, 0 },
    };
    int k1 = 1;
    System.out.println(sol.shortestPath(grid1, k1)); // Expected: 6

    int[][] grid2 = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 1, 0 }, { 0, 0, 0 } };
    int k2 = 2;
    System.out.println(sol.shortestPath(grid2, k2)); // Expected: 5

    int[][] grid3 = { { 0, 0, 1 }, { 0, 1, 0 }, { 1, 0, 0 }, { 0, 1, 0 } };
    int k3 = 3;
    System.out.println(sol.shortestPath(grid3, k3)); // Expected: 5
  }
}

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 , 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 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.
🤖 Don't fully get this? Learn it with Claude

Stuck on Shortest Path in a Grid with Obstacles Elimination? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Shortest Path in a Grid with Obstacles Elimination** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Shortest Path in a Grid with Obstacles Elimination** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Shortest Path in a Grid with Obstacles Elimination**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Shortest Path in a Grid with Obstacles Elimination**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes