1210. 穿过迷宫的最少移动次数
题目描述
你还记得那条风靡全球的贪吃蛇吗?
我们在一个 n*n 的网格上构建了新的迷宫地图,蛇的长度为 2,也就是说它会占去两个单元格。蛇会从左上角((0, 0) 和 (0, 1))开始移动。我们用 0 表示空单元格,用 1 表示障碍物。蛇需要移动到迷宫的右下角((n-1, n-2) 和 (n-1, n-1))。
每次移动,蛇可以这样走:
- 如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
- 如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
- 如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 90 度。蛇从((r, c)、(r, c+1))移动到 ((r, c)、(r+1, c))。
-
如果它处于竖直状态并且其右面的两个单元都是空的,就逆时针旋转 90 度。蛇从((r, c)、(r+1, c))移动到((r, c)、(r, c+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]]
输出: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]]
输出:9
提示
- 2 <= n <= 100
- 0 <= grid[i][j] <= 1
- 蛇保证从空单元格开始出发。
算法一:BFS
思路
-
这道题相当于求解 「求起点到终点的最短路长度」,因此可以使用 BFS 求解。
相比一般的网格图 BFS ,这道题多了个 「水平 / 竖直状态」,这可以通过添加一个维度来解决,也就是用 (x, y, s)表示 「蛇尾 在第 x 行第 y 列,s = 0 表示水平状态, s = 1 表示竖直状态」,因此初始位置为 (0, 0, 0),最终位置为(n-1, n-2, 0)。
-
移动方式有六种:
- 水平状态: 向下移动 / 向右移动 / 顺时针旋转 90 度;
- 竖直状态: 向下移动 / 向右移动 / 逆时针旋转 90 度;
-
其实这六种移动方式可以简化为三种移动方式:
- 向下移动: x 增加 1 ,y 和 s 不变,用三元组(1, 0, 0)表示;
- 向右移动: y 增加 1 ,x 和 s 不变, 用三元组(0, 1, 0)表示;
- 旋转: s 切换, 即 0 变为 1, 1 变为 0; x 和 y 不变,用三元组(0, 0, 1) 表示;
-
三元组中的数字表示(x,y,s) 每个值对应的变化量,对于旋转可以用异或 解决。
这样可以把 6 种移动方式用 3 个三元组表示,把这三个三元组存到数组 dirs 中, 遍历 dirs ,用同一份代码处理不同的移动。
-
边界判断:
- 移动后蛇身不能出界;
- 移动后蛇身不能在障碍物上;
- 对于旋转,还需要保证(x+1, y+1) 没有障碍物;
-
当蛇尾在(x, y) 时, 蛇头 呢 ?
如果 s = 0 ,蛇头在 (x, y+1);
如果 s = 1, 蛇头在 (x+1, y);合并表示为: (x+s, y+(s ^ 1))
收获
- 复习了 广度优先搜索 BFS,它可以用于找到最短路径,使用队列;
- 网格图,一般有多种移动方式的时候,使用数组存储,然后遍历数组,就可以使用一份代码处理所有的移动方式;
算法情况
- 时间复杂度:O(n2) ,其中 n 为 grid 的长度, vis 保证每个位置最多访问一次;
- 空间复杂度:O(n2)
代码
class Solution {
static constexpr int DIRS[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
public:
int minimumMoves(vector<vector<int>>& grid) {
int n = grid.size();
bool vis[n][n][2];
memset(vis, 0, sizeof(vis));
vis[0][0][0] = true; // 标记起点
vector<tuple<int, int, int>> q = {{0, 0, 0}}; // 初始位置
for(int step=1; !q.empty(); ++step){
vector<tuple<int, int, int>> nxt;
for(const auto & [X, Y, S] : q){
for(const auto &d : DIRS){
// 遍历三种移动方式
int x = X + d[0], y = Y + d[1], s = S ^ d[2]; // 蛇尾
int x2 = x + s, y2 = y + (s ^ 1); // 蛇头
if(x2 < n && y2 < n && !vis[x][y][s] &&
grid[x][y] == 0 && grid[x2][y2] == 0 &&
(d[2] == 0 || grid[x+1][y+1] == 0)){
if(x == n - 1 && y == n - 2)
return step; //走到终点
vis[x][y][s] = true;
nxt.emplace_back(x, y, s);
}
}
}
// 相当于把nxt指向的区域赋值给q
q = move(nxt);
}
return -1;
}
};
参考资料: