leetcode
leetcode 2001 ~ 2050
到达角落需要移除障碍物的最小数目

到达角落需要移除障碍物的最小数目

难度:

标签:

题目描述

给你一个下标从 0 开始的二维整数数组 grid ,数组大小为 m x n 。每个单元格都是两个值之一:

  • 0 表示一个 单元格,
  • 1 表示一个可以移除的 障碍物

你可以向上、下、左、右移动,从一个空单元格移动到另一个空单元格。

现在你需要从左上角 (0, 0) 移动到右下角 (m - 1, n - 1) ,返回需要移除的障碍物的 最小 数目。

 

示例 1:

输入:grid = [[0,1,1],[1,1,0],[1,1,0]]
输出:2
解释:可以移除位于 (0, 1) 和 (0, 2) 的障碍物来创建从 (0, 0) 到 (2, 2) 的路径。
可以证明我们至少需要移除两个障碍物,所以返回 2 。
注意,可能存在其他方式来移除 2 个障碍物,创建出可行的路径。

示例 2:

输入:grid = [[0,1,0,0,0],[0,1,0,1,0],[0,0,0,1,0]]
输出:0
解释:不移除任何障碍物就能从 (0, 0) 到 (2, 4) ,所以返回 0 。

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 105
  • 2 <= m * n <= 105
  • grid[i][j]0 1
  • grid[0][0] == grid[m - 1][n - 1] == 0

代码结果

运行时间: 691 ms, 内存: 41.9 MB


/*
 * 思路:
 * 使用广度优先搜索(BFS)和Java Stream API的组合来解决此问题。虽然Stream并不是非常适合处理状态变化和队列操作,
 * 但我们可以在某些情况下使用Stream来简化代码。
 *
 * 具体步骤:
 * 1. 使用优先队列实现BFS,计算到达每个位置的最小障碍物移除数量。
 * 2. 使用Stream API处理方向数组并简化代码的某些部分。
 */

import java.util.*;
import java.util.stream.IntStream;

public class SolutionStream {
    private static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    public int minObstacleRemoval(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dist = new int[m][n];
        for (int i = 0; i < m; i++) {
            Arrays.fill(dist[i], Integer.MAX_VALUE);
        }
        dist[0][0] = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
        pq.offer(new int[]{0, 0, 0});

        while (!pq.isEmpty()) {
            int[] curr = pq.poll();
            int x = curr[0], y = curr[1], obstacles = curr[2];
            if (x == m - 1 && y == n - 1) {
                return obstacles;
            }
            IntStream.range(0, 4).forEach(i -> {
                int nx = x + DIRECTIONS[i][0], ny = y + DIRECTIONS[i][1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int nextObstacles = obstacles + grid[nx][ny];
                    if (nextObstacles < dist[nx][ny]) {
                        dist[nx][ny] = nextObstacles;
                        pq.offer(new int[]{nx, ny, nextObstacles});
                    }
                }
            });
        }
        return -1;
    }
}

解释

方法:

本题解采用了广度优先搜索(BFS)的策略来找到从起点到终点移除最少障碍物的路径。为了实现这一点,代码使用了双端队列(deque)来处理两种情况:移动到空单元格和移动到障碍物单元格。如果是空单元格,则优先处理(加入队列前端),如果是障碍物,则稍后处理(加入队列尾部)。这样做的目的是尽可能地避免移除障碍物。同时,使用了一个二维数组 `distances` 来记录到每个位置的最小障碍物移除数,这有助于避免不必要的重复访问和更新。当达到终点时,即可返回当前位置的障碍物移除数。

时间复杂度:

O(m*n)

空间复杂度:

O(m*n)

代码细节讲解

🦆
在广度优先搜索(BFS)中使用双端队列(deque)的优势是什么,相比于普通队列有何不同?
在广度优先搜索(BFS)中使用双端队列(deque)可以有效地支持在队列的前端和后端快速添加和移除元素。这种灵活性相比于普通队列(通常只支持在尾部添加元素和在头部移除元素)更适合某些场景。在本题中,使用deque可以根据当前元素的类型(是否为障碍物)来决定是将新的元素加到队列的前端还是后端,这种策略可以优化搜索过程,使得路径中遇到障碍物较少的路线被优先考虑,从而更快找到最优解。
🦆
为什么在处理到达每个单元格时考虑加入队列前端或队列尾部,这种区分对算法效率有何影响?
在处理到达每个单元格时,将非障碍物单元格加入队列前端,障碍物单元格加入队列尾部的区分方法,能够确保路径中较少障碍物的路线被优先处理。这种策略类似于'0-1 BFS',用于处理图中边权重为0或1的最短路径问题。通过这种方式,算法优先探索不需要移除障碍物的路径,从而减少总的移除次数,提升算法效率,尤其是在大型网格中寻找最短路径时更为明显。
🦆
二维数组 `distances` 初始化为一个非常大的数(例如900000),这个数的选取有什么特别的意义或考虑吗?
在本题中,二维数组 `distances` 用于存储到达每个单元格时所需移除的最小障碍物数量。初始化为一个非常大的数(如900000)是为了确保在初次访问任何单元格时,可以通过比较发现更小的障碍物移除数,并更新该位置。这个数值只需要大于任何可能的障碍物移除次数,通常选取一个大于网格大小乘最大障碍物数的安全值,以避免在比较时发生错误的未更新情况。
🦆
如果在某个位置 `(x, y)` 的 `distances[x][y]` 值已经是最小障碍物移除数,再次更新这个位置是否有可能?这会不会影响算法的效率?
如果某个位置 `(x, y)` 的 `distances[x][y]` 已经是到达该位置时的最小障碍物移除数,理论上不应再有更小的值来更新它,因为每次访问时都是基于当前最优路径的结果。但在实际BFS过程中,可能会多次访问同一位置,特别是当路径中有回环时。这种重复访问确实会影响算法的效率,因为它增加了队列操作和不必要的计算。因此,确保每次只有当找到更优的路径时才更新`distances`并重新加入队列,是避免无效计算并提升算法效率的关键。

相关问题