搜索题目:穿过迷宫的最少移动次数

文章目录

题目

标题和出处

标题:穿过迷宫的最少移动次数

出处:1210. 穿过迷宫的最少移动次数

难度

7 级

题目描述

要求

在一个 n × n \texttt{n} \times \texttt{n} n×n 的网格,有一条占据两个单元格的蛇,蛇从左上角 (0, 0) \texttt{(0, 0)} (0, 0) 和 (0, 1) \texttt{(0, 1)} (0, 1) 开始移动。网格中的 0 \texttt{0} 0 表示空单元格, 1 \texttt{1} 1 表示障碍物。蛇需要移动到迷宫的右下角 (n − 1 , n − 2) \texttt{(n} - \texttt{1}\texttt{, n} - \texttt{2)} (n−1, n−2) 和 (n − 1 , n − 1) \texttt{(n} - \texttt{1}\texttt{, n} - \texttt{1)} (n−1, n−1)。

每次移动,蛇可能的操作有:

  • 如果右边没有障碍,则向右移动一个单元格。并仍然保持身体的水平或竖直方向。
  • 如果下边没有障碍,则向下移动一个单元格。并仍然保持身体的水平或竖直方向。
  • 如果蛇处于水平方向并且其下边的两个单元都是空的,则顺时针旋转,从 (r, c) \texttt{(r, c)} (r, c)、 (r, c + 1) \texttt{(r, c} + \texttt{1)} (r, c+1) 移动到 (r, c) \texttt{(r, c)} (r, c)、 (r + 1, c) \texttt{(r} + \texttt{1, c)} (r+1, c)。
  • 如果蛇处于竖直方向并且其右边的两个单元都是空的,则逆时针旋转,从 (r, c) \texttt{(r, c)} (r, c)、 (r + 1, c) \texttt{(r} + \texttt{1, c)} (r+1, c) 移动到 (r, c) \texttt{(r, c)} (r, c)、 (r, c + 1) \texttt{(r, c} + \texttt{1)} (r, c+1)。

返回蛇到达目的地所需的最少移动次数。

如果无法到达目的地,返回 -1 \texttt{-1} -1。

示例

示例 1:

输入: grid = [[0,0,0,0,0,1],[1,1,0,0,1,0],[0,0,0,0,1,1],[0,0,1,0,1,0],[0,1,1,0,0,0],[0,1,1,0,0,0]] \texttt{grid = [[0,0,0,0,0,1],[1,1,0,0,1,0],[0,0,0,0,1,1],[0,0,1,0,1,0],[0,1,1,0,0,0],[0,1,1,0,0,0]]} grid = [[0,0,0,0,0,1],[1,1,0,0,1,0],[0,0,0,0,1,1],[0,0,1,0,1,0],[0,1,1,0,0,0],[0,1,1,0,0,0]]

输出: 11 \texttt{11} 11

解释:一种可能的解决方案是 [右, 右, 顺时针旋转, 右, 下, 下, 下, 下, 逆时针旋转, 右, 下]。

示例 2:

输入: grid = [[0,0,1,1,1,1],[0,0,0,0,1,1],[1,1,0,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,0]] \texttt{grid = [[0,0,1,1,1,1],[0,0,0,0,1,1],[1,1,0,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,0]]} grid = [[0,0,1,1,1,1],[0,0,0,0,1,1],[1,1,0,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,1],[1,1,1,0,0,0]]

输出: 9 \texttt{9} 9

数据范围

  • 2 ≤ n ≤ 100 \texttt{2} \le \texttt{n} \le \texttt{100} 2≤n≤100
  • 0 ≤ grid[i][j] ≤ 1 \texttt{0} \le \texttt{grid[i][j]} \le \texttt{1} 0≤grid[i][j]≤1
  • 保证蛇从空单元格开始出发

解法

思路和算法

计算最少移动次数可以使用广度优先搜索实现。这道题中,蛇占据两个单元格,且蛇的身体可以是水平方向或竖直方向,因此情况较为复杂,广度优先搜索需要记录的状态有蛇尾所在单元格与蛇的身体方向。

定义三维数组 distances \textit{distances} distances 记录蛇从初始状态到每个状态的最少移动次数。规定方向状态的 0 0 0 表示水平方向, 1 1 1 表示竖直方向,则 distances [ i ] [ j ] [ k ] \textit{distances}[i][j][k] distances[i][j][k] 表示蛇从初始状态到蛇尾位于 ( i , j ) (i, j) (i,j) 且蛇的身体方向是 k k k 的状态的最少移动步数。由于初始时蛇尾位于 ( 0 , 0 ) (0, 0) (0,0) 且蛇的身体是水平方向,因此 distances [ 0 ] [ 0 ] [ 0 ] = 0 \textit{distances}[0][0][0] = 0 distances[0][0][0]=0。将 distances \textit{distances} distances 中的其余元素初始化为无穷大。

每次移动的可能操作包括向右移动、向下移动、从水平方向顺时针旋转到竖直方向、从竖直方向逆时针旋转到水平方向。在确定蛇的身体方向时,旋转操作的可能性唯一,因此每次移动的可能操作只有三种。

对于每个状态,根据蛇尾所在行下标和列下标以及蛇的身体方向,计算蛇头所在行下标和列下标。将蛇尾所在行下标和列下标分别记为 tailRow \textit{tailRow} tailRow 和 tailCol \textit{tailCol} tailCol,将蛇头所在行下标和列下标分别记为 headRow \textit{headRow} headRow 和 headCol \textit{headCol} headCol,则计算蛇头所在行下标和列下标的方法如下。

  • 如果蛇的身体是水平方向,则 headRow = tailRow \textit{headRow} = \textit{tailRow} headRow=tailRow, headCol = tailCol + 1 \textit{headCol} = \textit{tailCol} + 1 headCol=tailCol+1。

  • 如果蛇的身体是竖直方向,则 headRow = tailRow + 1 \textit{headRow} = \textit{tailRow} + 1 headRow=tailRow+1, headCol = tailCol \textit{headCol} = \textit{tailCol} headCol=tailCol。

根据蛇尾和蛇头所在下标以及蛇的身体方向,判断三种操作是否可行。

  • 如果蛇尾和蛇头的右边单元格在网格范围内且都是空单元格,则可以向右移动。

  • 如果蛇尾和蛇头的下边单元格在网格范围内且都是空单元格,则可以向下移动。

  • 如果蛇尾所在单元格的右边、下边和右下单元格在网格范围内且都是空单元格,则可以旋转。

对于可行的操作到达的后续状态,如果后续状态未访问,则更新后续状态的最少移动次数,继续访问后续状态。

当所有可以到达的状态都遍历结束时,获得目的地对应的最少移动次数。

  • 如果目的地对应的最少移动次数不是无穷大,则可以到达目的地,返回最少移动次数。

  • 如果目的地对应的最少移动次数是无穷大,则不能到达目的地,返回 − 1 -1 −1。

实现方面,可以定义方向数组简化代码。

代码

java 复制代码
class Solution {
    static int[][] dirs = {{0, 1}, {1, 0}};

    public int minimumMoves(int[][] grid) {
        int n = grid.length;
        int[][][] distances = new int[n][n][2];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                Arrays.fill(distances[i][j], Integer.MAX_VALUE);
            }
        }
        distances[0][0][0] = 0;
        Queue<int[]> queue = new ArrayDeque<int[]>();
        queue.offer(new int[]{0, 0, 0});
        while (!queue.isEmpty()) {
            int[] state = queue.poll();
            int tailRow = state[0], tailCol = state[1], headDir = state[2];
            int distance = distances[tailRow][tailCol][headDir];
            int headRow = tailRow + dirs[headDir][0];
            int headCol = tailCol + dirs[headDir][1];
            for (int[] dir : dirs) {
                int newTailRow = tailRow + dir[0], newTailCol = tailCol + dir[1], newHeadRow = headRow + dir[0], newHeadCol = headCol + dir[1];
                if (newHeadRow >= 0 && newHeadRow < n && newHeadCol >= 0 && newHeadCol < n) {
                    if (grid[newTailRow][newTailCol] == 0 && grid[newHeadRow][newHeadCol] == 0 && distances[newTailRow][newTailCol][headDir] == Integer.MAX_VALUE) {
                        distances[newTailRow][newTailCol][headDir] = distance + 1;
                        queue.offer(new int[]{newTailRow, newTailCol, headDir});
                    }
                }
            }
            int newHeadDir = headDir ^ 1;
            if (tailRow < n - 1 && tailCol < n - 1 && grid[tailRow + dirs[newHeadDir][0]][tailCol + dirs[newHeadDir][1]] == 0 && grid[tailRow + 1][tailCol + 1] == 0) {
                if (distances[tailRow][tailCol][newHeadDir] == Integer.MAX_VALUE) {
                    distances[tailRow][tailCol][newHeadDir] = distance + 1;
                    queue.offer(new int[]{tailRow, tailCol, newHeadDir});
                }
            }
        }
        return distances[n - 1][n - 2][0] != Integer.MAX_VALUE ? distances[n - 1][n - 2][0] : -1;
    }
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格的边长。广度优先搜索的状态数是 2 n 2 2n^2 2n2,每个状态最多访问一次。

  • 空间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是网格的边长。广度优先搜索需要创建 n × n × 2 n \times n \times 2 n×n×2 的三维数组记录每个状态的最少移动次数,队列中的元素个数是 O ( n 2 ) O(n^2) O(n2)。

相关推荐
汉克老师3 天前
GESP6级C++考试语法知识(二十七、广度优先搜索(二、二维BFS))
c++·算法·图论·宽度优先·广度优先搜索·gesp6级·gesp六级
伟大的车尔尼6 天前
搜索题目:为高尔夫比赛砍树
广度优先搜索
伟大的车尔尼7 天前
搜索题目:滑动谜题
广度优先搜索
伟大的车尔尼8 天前
搜索题目:颜色交替的最短路径
广度优先搜索
伟大的车尔尼12 天前
搜索题目:验证二叉树
并查集·深度优先搜索·广度优先搜索
伟大的车尔尼19 天前
搜索题目:单词接龙
广度优先搜索
伟大的车尔尼20 天前
搜索题目:最小基因变化
广度优先搜索
伟大的车尔尼23 天前
搜索题目:可能的二分法
并查集·深度优先搜索·广度优先搜索
伟大的车尔尼1 个月前
搜索题目:边界着色
深度优先搜索·广度优先搜索