Leetcode6081. 到达角落需要移除障碍物的最小数目-----0-1BFS
题目表述
给你一个下标从 0 开始的二维整数数组 grid ,数组大小为 m x n 。每个单元格都是两个值之一:
- 0 表示一个 空 单元格,
- 1 表示一个可以移除的 障碍物 。
你可以向上、下、左、右移动,从一个空单元格移动到另一个空单元格。
现在你需要从左上角 (0, 0) 移动到右下角 (m - 1, n - 1) ,返回需要移除的障碍物的 最小 数目。
示例:
输入:grid = [[0,1,1],[1,1,0],[1,1,0]]
输出:2
解释:可以移除位于 (0, 1) 和 (0, 2) 的障碍物来创建从 (0, 0) 到 (2, 2) 的路径。
可以证明我们至少需要移除两个障碍物,所以返回 2 。
注意,可能存在其他方式来移除 2 个障碍物,创建出可行的路径。
0-1广度优先搜索
常规的广度优先搜索可以找出在边权均为1时的单源最短路,然而在建模中,边权除了1之外也可能为0。
在常规的广度优先搜索中,我们使用队列作为维护节点的数据结构,就保证了从队列中取出的节点,它们与源点之间的距离是单调递增的。然而如果边权可能为0,就会出现如下的情况:
- 源点s被取出队列;
-
源点 s 到节点 v1 有一条权值为 11 的边,将节点 v1 加入队列;
-
源点 s到节点 v2 有一条权值为 0 的边,将节点 v2 加入队列;
此时节点v2就会在节点v1之后被取出队列,但节点v2与原点之间的距离反而较小,这样就破坏了广度优先搜索正确性的基础。
可以使用双端队列代替普通的队列作为维护节点的数据结构,当任意节点u被取出队列时,如果它到某节点vi有一条权值为0的边,那么就将节点vi加入双端队列的队首,如果它到某节点vj有一条权值为1的边,那么和常规的bfs相同,将节点vj加入到双端队列的队尾。
0-1 广度优先搜索的实现其实与 Dijkstra 算法非常相似。在 Dijkstra 算法中,我们用优先队列保证了距离的单调递增性。而在 0-1 广度优先搜索中,实际上任意时刻队列中的节点与源点的距离均为 d 或 d+1(其中 d 为某一非负整数),并且所有与源点距离为 d 的节点都出现在队首附近,所有与源点距离为 d + 1 的节点都出现在队尾附近。因此,只要使用双端队列,对于边权为 0 和 1 的两种情况分别将对应节点添加至队首和队尾,就保证了距离的单调递增性。
class Solution {
public int minimumObstacles(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dis = new int[m][n];
for(int i = 0; i < m;i++){
Arrays.fill(dis[i], Integer.MAX_VALUE);
}
dis[0][0] = 0;
int[][] dirs = {{1,0}, {-1, 0}, {0, -1}, {0,1}};
Deque<int[]> que = new ArrayDeque<>();
que.offer(new int[]{0,0});
while(!que.isEmpty()){
int[] cur = que.poll();
for(int[] dir : dirs){
int x = dir[0] + cur[0];
int y = dir[1] + cur[1];
if(x < 0 || y < 0 || x >= m || y >= n) continue;
if(dis[cur[0]][cur[1]] + grid[x][y] < dis[x][y]){
dis[x][y] = dis[cur[0]][cur[1]] + grid[x][y];
if(grid[x][y] == 0) que.addFirst(new int[]{x,y});
else{
que.addLast(new int[]{x,y});
}
}
}
}
return dis[m-1][n-1];
}
}