Loading problems...
TL;DR: Model the grid as a weighted graph where moving to an obstacle costs 1 and moving to an empty cell costs 0, then use Dijkstra's algorithm to find the minimum cost path.
In the Minimum Obstacle Removal to Reach Corner problem, you are provided with a 2D grid containing 0s (empty cells) and 1s (obstacles). Your objective is to traverse from the top-left corner (0, 0) to the bottom-right corner (m-1, n-1). While you can move freely through empty cells, traversing a cell containing a 1 requires removing that obstacle. The goal is to determine the minimum number of obstacles that must be removed to establish a valid path.
This is a classic shortest path problem on a grid. Unlike a standard maze where obstacles are impassable, here obstacles simply incur a cost. This transforms the problem into finding a path with the minimum total weight, making it a perfect candidate for LeetCode 2290 Solution strategies involving weighted graph traversal.
A naive approach to solving this problem is to use Depth First Search (DFS) to explore every possible path from the start to the destination. For every path found, we count the number of obstacles encountered and track the global minimum.
textfunction dfs(row, col, current_obstacles): if (row, col) is destination: min_obstacles = min(min_obstacles, current_obstacles) return mark (row, col) as visited for each neighbor: cost = grid[neighbor_row][neighbor_col] dfs(neighbor_row, neighbor_col, current_obstacles + cost) unmark (row, col) // backtrack
This approach fails due to Time Limit Exceeded (TLE).
The key intuition is to treat the grid as a graph where each cell is a node and adjacent cells are connected by directed edges. The weight of an edge directed into a cell depends on the value of that cell:
0 has a weight of 0.1 has a weight of 1.Standard Breadth-First Search (BFS) only finds the shortest path in an unweighted graph (or where all weights are equal). Because our weights differ ( vs ), standard BFS is insufficient as it assumes the first time you reach a node, it is via the shortest path. This assumption holds for hop-count distance but not for weighted distance.
Dijkstra's Algorithm addresses this by using a Priority Queue (Min-Heap) to explore paths. The invariant enforced by Dijkstra is that we always expand the node with the current lowest cumulative cost. This ensures that when we first extract the destination node from the priority queue, we have guaranteed the minimum number of obstacles removed.
Visual Description:
Imagine a contour map. The algorithm starts at (0,0) with a "height" (cost) of 0. It expands like water flowing downhill. It floods all reachable 0-cost cells instantly. When it hits a 1-cost cell (a "wall"), the flow pauses until all lower "height" areas are filled. The Priority Queue manages this flow, ensuring we process all possible paths costing obstacles before considering any path costing obstacles.

We will implement Dijkstra's algorithm strictly following the pattern:
min_obstacles (or dist) of size , initialized to infinity (INT_MAX). min_obstacles[i][j] stores the minimum obstacles removed to reach cell (i, j).(cost, row, col). The heap orders elements primarily by cost in ascending order.min_obstacles[0][0] = 0.(0, 0, 0) onto the heap.(current_cost, r, c).current_cost is greater than min_obstacles[r][c], this is an outdated path; skip it.(nr, nc):
new_cost = current_cost + grid[nr][nc].new_cost < min_obstacles[nr][nc]:
min_obstacles[nr][nc] = new_cost.(new_cost, nr, nc) to the heap.(m-1, n-1) from the heap, its cost is the answer. Alternatively, return min_obstacles[m-1][n-1] after the heap is empty.Let's trace the logic for a small example.
[(0, 0, 0)]. Distance matrix is all except dist[0][0]=0.(0,1) and (1,0).
grid[0][1] is 1 (obstacle). Cost becomes . Push (1, 0, 1). Update dist[0][1]=1.grid[1][0] is 0 (empty). Cost becomes . Push (0, 1, 0). Update dist[1][0]=0.[(0, 1, 0), (1, 0, 1)].
(0, 1, 0) because it has the lower cost (Greedy choice).(1, 0).(m-1, n-1) are popped from the PQ. The associated cost is returned immediately.Dijkstra's algorithm is proven to find the shortest path in a graph with non-negative edge weights.
grid values are only 0 or 1, weights are non-negative, satisfying the requirement for Dijkstra's correctness.cpp#include <vector> #include <queue> #include <tuple> using namespace std; class Solution { public: int minimumObstacles(vector<vector<int>>& grid) { int m = grid.size(); int n = grid[0].size(); // Min-heap storing {obstacles, row, col} // Orders by obstacles ascending priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq; // Distance matrix to track minimum obstacles to each cell vector<vector<int>> dist(m, vector<int>(n, 1e9)); // Directions array for moving up, down, left, right int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // Start at (0, 0) with 0 cost dist[0][0] = 0; pq.push({0, 0, 0}); while (!pq.empty()) { auto [cost, r, c] = pq.top(); pq.pop(); // If we reached the target, return the cost if (r == m - 1 && c == n - 1) { return cost; } // Optimization: If current path is worse than already found, skip if (cost > dist[r][c]) { continue; } // Explore neighbors for (auto& dir : dirs) { int nr = r + dir[0]; int nc = c + dir[1]; // Check bounds if (nr >= 0 && nr < m && nc >= 0 && nc < n) { int newCost = cost + grid[nr][nc]; // If a cheaper path is found, update and push to PQ if (newCost < dist[nr][nc]) { dist[nr][nc] = newCost; pq.push({newCost, nr, nc}); } } } } return -1; // Should not reach here } };
javaimport java.util.PriorityQueue; import java.util.Arrays; class Solution { public int minimumObstacles(int[][] grid) { int m = grid.length; int n = grid[0].length; // Priority Queue stores int[] {cost, row, col} // Ordered by cost ascending PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]); // Distance matrix int[][] dist = new int[m][n]; for (int[] row : dist) { Arrays.fill(row, Integer.MAX_VALUE); } int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // Start at (0, 0) dist[0][0] = 0; pq.offer(new int[]{0, 0, 0}); while (!pq.isEmpty()) { int[] current = pq.poll(); int cost = current[0]; int r = current[1]; int c = current[2]; if (r == m - 1 && c == n - 1) { return cost; } // Optimization: Skip stale entries if (cost > dist[r][c]) { continue; } for (int[] dir : dirs) { int nr = r + dir[0]; int nc = c + dir[1]; if (nr >= 0 && nr < m && nc >= 0 && nc < n) { int newCost = cost + grid[nr][nc]; if (newCost < dist[nr][nc]) { dist[nr][nc] = newCost; pq.offer(new int[]{newCost, nr, nc}); } } } } return -1; } }
pythonimport heapq class Solution: def minimumObstacles(self, grid: list[list[int]]) -> int: m, n = len(grid), len(grid[0]) # Priority Queue stores (cost, row, col) pq = [(0, 0, 0)] # Distance matrix initialized to infinity dist = [[float('inf')] * n for _ in range(m)] dist[0][0] = 0 dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] while pq: cost, r, c = heapq.heappop(pq) if r == m - 1 and c == n - 1: return cost # Optimization: Skip if we found a shorter path to this cell already if cost > dist[r][c]: continue for dr, dc in dirs: nr, nc = r + dr, c + dc if 0 <= nr < m and 0 <= nc < n: new_cost = cost + grid[nr][nc] if new_cost < dist[nr][nc]: dist[nr][nc] = new_cost heapq.heappush(pq, (new_cost, nr, nc)) return -1
Time Complexity:
Space Complexity:
min_obstacles (distance) array.The Graph - Shortest Path (Dijkstra's Algorithm) pattern is a cornerstone of interview preparation. Understanding LeetCode 2290 enables you to solve:
Mastering the priority queue mechanism in LeetCode 2290 provides the template for all the above problems.
new_cost < dist[nr][nc]if (cost > dist[r][c]) continue; after popping from the heap.