文章目录
题目
标题和出处
标题:穿过迷宫的最少移动次数
难度
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)。