【LeetCode】1210. 穿过迷宫的最少移动次数

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;
    }
};

参考资料:

  1. 广度优先搜索(BFS)基本概念

  2. C++:move,带你从根本理解move函数是什么

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值