leetcode
leetcode 1201 ~ 1250
网格中的最短路径

网格中的最短路径

难度:

标签:

题目描述

给你一个 m * n 的网格,其中每个单元格不是 0(空)就是 1(障碍物)。每一步,您都可以在空白单元格中上、下、左、右移动。

如果您 最多 可以消除 k 个障碍物,请找出从左上角 (0, 0) 到右下角 (m-1, n-1) 的最短路径,并返回通过该路径所需的步数。如果找不到这样的路径,则返回 -1 。

 

示例 1:

输入: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1
输出:6
解释:
不消除任何障碍的最短路径是 10。
消除位置 (3,2) 处的障碍后,最短路径是 6 。该路径是 (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2).

示例 2:

输入:grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1
输出:-1
解释:我们至少需要消除两个障碍才能找到这样的路径。

 

提示:

  • grid.length == m
  • grid[0].length == n
  • 1 <= m, n <= 40
  • 1 <= k <= m*n
  • grid[i][j] 是 0 或 1
  • grid[0][0] == grid[m-1][n-1] == 0

代码结果

运行时间: 43 ms, 内存: 16.7 MB


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

/**
 * This method uses Java Streams to help with the BFS approach to find the shortest path in a grid with obstacles.
 * It leverages streams for initializing and processing parts of the grid.
 */
public class Solution {
    public int shortestPath(int[][] grid, int k) {
        int m = grid.length;
        int n = grid[0].length;
        int[][][] visited = new int[m][n][k + 1];
        IntStream.range(0, m).forEach(i -> IntStream.range(0, n).forEach(j -> Arrays.fill(visited[i][j], Integer.MAX_VALUE)));
        int[] dirs = {0, 1, 0, -1, 0};
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0, 0, 0});
        visited[0][0][0] = 0;

        while (!queue.isEmpty()) {
            int[] cur = queue.poll();
            int x = cur[0], y = cur[1], steps = cur[2], obstacles = cur[3];

            if (x == m - 1 && y == n - 1) {
                return steps;
            }

            for (int i = 0; i < 4; i++) {
                int nx = x + dirs[i];
                int ny = y + dirs[i + 1];

                if (nx >= 0 && ny >= 0 && nx < m && ny < n) {
                    int newObstacles = obstacles + grid[nx][ny];

                    if (newObstacles <= k && steps + 1 < visited[nx][ny][newObstacles]) {
                        visited[nx][ny][newObstacles] = steps + 1;
                        queue.offer(new int[]{nx, ny, steps + 1, newObstacles});
                    }
                }
            }
        }

        return -1;
    }
}

解释

方法:

这题解使用了宽度优先搜索(BFS)来找到从网格的左上角到右下角的最短路径。这里的关键是可以选择消除最多k个障碍物。为了处理这个条件,搜索状态不仅包括当前位置坐标 (x, y),还包括一个剩余障碍物消除次数 `rest`。状态被保存在一个队列中,每个状态为 (x, y, rest),其中 `rest` 表示还可以消除的障碍物数量。为防止重复处理相同的状态,使用一个集合 `visited` 来记录已经访问过的状态。搜索过程中,对于每个状态,都尝试向四个方向移动,如果是空白格并且该状态未被访问,则将其加入队列;如果是障碍物且 `rest` 大于0,则消除障碍物并将新状态加入队列。如果能到达终点,则返回当前步数。

时间复杂度:

O(m*n*k)

空间复杂度:

O(m*n*k)

代码细节讲解

🦆
在算法中,如果起始位置`(0, 0)`或终点位置`(m-1, n-1)`本身就是障碍物,这种情况下该如何处理?
如果起始位置`(0, 0)`是障碍物,且障碍消除次数`k`大于0,则可以将其视为一个可通过的位置并开始搜索。如果`k`等于0,则无法开始搜索,返回-1。对于终点位置`(m-1, n-1)`,如果它是障碍物并且在到达该位置时还有剩余的障碍消除次数,则可以消除这个障碍物并到达终点。如果没有剩余次数,即使到达终点附近也无法完成任务,返回-1。
🦆
你在代码中提到如果`k >= m + n - 3`就可以直接返回`m + n - 2`,这个判断的依据是什么?为什么是`m + n - 3`而不是其他值?
这个判断基于最短路径的理论计算,其中最短路径是从左上角到右下角的直线路径,这个路径的长度为`m + n - 2`。在这条路径中,除了起始点和终点外,最多有`m + n - 3`个其他点可能是障碍物。因此,如果`k`的值大于或等于`m + n - 3`,意味着可以移除路径上所有可能的障碍物,直接沿着最短路径走到终点。
🦆
在使用BFS处理时,为什么选择使用`(x, y, rest)`作为状态而不是只用`(x, y)`?这样做的优势和可能带来的问题是什么?
使用`(x, y, rest)`作为状态让算法能够区分在不同剩余障碍物消除次数下的位置状态。这样做的优势在于可以有效处理每个位置在不同消除次数下的访问情况,防止因障碍物而无法进一步探索的情况。然而,这也带来了内存使用增加的问题,因为状态空间变大,相应的`visited`集合也需要存储更多元素,增加了内存占用。
🦆
在尝试移动到下一个位置`(nx, ny)`时,你会检查`(nx, ny, rest)`或`(nx, ny, rest-1)`是否已被访问。这里的访问集合`visited`是如何确保算法效率的,同时它可能存在哪些潜在的问题?
`visited`集合通过记录已访问的状态,帮助避免重复处理相同状态,从而提高算法效率。然而,这种方法可能导致高内存消耗,因为每个不同的`(x, y, rest)`组合都需要被记录。此外,如果`k`值很大,`visited`集合的大小会显著增加,可能会导致内存溢出或降低处理速度。

相关问题