Leetcode.1263 推箱子

文章描述了一个关于LeetCode上的推箱子问题,该问题是典型的益智游戏,玩家需将一个箱子推到目标位置。给定一个表示游戏地图的网格,玩家(S)和箱子(B)的位置,目标(T)的位置,任务是找出将箱子推到目标的最小推动次数。文章提出了使用广度优先搜索(BFS)策略来解决此问题,通过压缩状态并以01-BFS的方式遍历所有可能的移动路径,寻找最小操作数。如果无法将箱子推到目标,则返回-1。
摘要由CSDN通过智能技术生成

题目链接

Leetcode.1263 推箱子 rating : 2297

题目描述

「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。

游戏地图用大小为 m × n m \times n m×n 的网格 g r i d grid grid 表示,其中每个元素可以是墙、地板或者是箱子。

现在你将作为玩家参与游戏,按规则将箱子 'B'移动到目标位置 'T'

  • 玩家用字符 'S'表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
  • 地板用字符 '.'表示,意味着可以自由行走。
  • 墙用字符 '#'表示,意味着障碍物,不能通行。
  • 箱子仅有一个,用字符 'B'表示。相应地,网格上有一个目标位置 'T'
  • 玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
  • 玩家无法越过箱子。

返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 − 1 -1 1

示例 1:

在这里插入图片描述

输入:grid = [[“#”,“#”,“#”,“#”,“#”,“#”],
[“#”,“T”,“#”,“#”,“#”,“#”],
[“#”,“.”,“.”,“B”,“.”,“#”],
[“#”,“.”,“#”,“#”,“.”,“#”],
[“#”,“.”,“.”,“.”,“S”,“#”],
[“#”,“#”,“#”,“#”,“#”,“#”]]
输出:3
解释:我们只需要返回推箱子的次数。

示例 2:

输入:grid = [[“#”,“#”,“#”,“#”,“#”,“#”],
[“#”,“T”,“#”,“#”,“#”,“#”],
[“#”,“.”,“.”,“B”,“.”,“#”],
[“#”,“#”,“#”,“#”,“.”,“#”],
[“#”,“.”,“.”,“.”,“S”,“#”],
[“#”,“#”,“#”,“#”,“#”,“#”]]
输出:-1

示例 3:

输入:grid = [[“#”,“#”,“#”,“#”,“#”,“#”],
[“#”,“T”,“.”,“.”,“#”,“#”],
[“#”,“.”,“#”,“B”,“.”,“#”],
[“#”,“.”,“.”,“.”,“.”,“#”],
[“#”,“.”,“.”,“.”,“S”,“#”],
[“#”,“#”,“#”,“#”,“#”,“#”]]
输出:5
解释:向下、向左、向左、向上再向上。

提示:
  • m = g r i d . l e n g t h m = grid.length m=grid.length
  • n = g r i d [ i ] . l e n g t h n = grid[i].length n=grid[i].length
  • 1 ≤ m , n ≤ 20 1 \leq m, n \leq 20 1m,n20
  • g r i d grid grid 仅包含字符 '.', '#', 'S' , 'T', 以及 'B'
  • g r i d grid grid'S', 'B' 和 'T'各只能出现一个。

解法:01 bfs

我们把 (箱子的位置,玩家的位置) 看成是一个整体的状态。

我们记 m = g r i d . s i z e ( ) , n = g r i d [ 0 ] . s i z e ( ) m = grid.size() , n = grid[0].size() m=grid.size(),n=grid[0].size(),箱子的位置是 ( b x , b y ) (bx,by) (bx,by),玩家的位置是 ( s x , s y ) (sx,sy) (sx,sy)

我们定义 f ( 箱子的位置,玩家的位置 ) f(箱子的位置,玩家的位置) f(箱子的位置,玩家的位置) 为处于这个状态的最小操作数,那么我们最终的答案就是 f ( ′ T ′ 的位置,玩家的位置 ) f('T' 的位置,玩家的位置) f(T的位置,玩家的位置),因为我们只需要把 箱子推到终点 'T'即可,这样看来 终点可能有多个,我们只需要求距离起点路径和最小的那一个即可。(将整体看成是遍历起点到终点的一张图)

为了压缩状态,我们可以定义箱子的位置为 : b = b x × n + b y b = bx \times n + by b=bx×n+by;定义玩家的位置为 : s = s x × n + s y s = sx \times n + sy s=sx×n+sy,即 f ( b , s ) f(b,s) f(b,s)

起点就是 初始箱子的位置 也就是 'B'所在的位置 ( b x , b y ) (bx,by) (bx,by),初始玩家的位置 也就是 'S'所在的位置 ( s x , s y ) (sx,sy) (sx,sy)。即 f ( b x × n + b y , s x × n + s y ) f(bx \times n + by , sx \times n + sy) f(bx×n+by,sx×n+sy)就是起点的状态,起点状态初始为 0 0 0

对于 推箱子 的操作,我们才让操作数 + 1 +1 +1;只移动 玩家位置 的操作,我们只更新状态。

进行 01 bfs

首先出队,如果当前状态的 箱子位置就是终点位置,那么就直接返回 当前状态的最小操作数

我们先让 玩家 向 上下左右四个方向移动。

如果 玩家 的新位置是合法的,并且移到了箱子所在的位置,那么箱子就要被推走(因为箱子的位置已经被占了),此时如果状态合法的话,就将 当前状态更新为 = 旧状态 + 1 当前状态更新为 = 旧状态 + 1 当前状态更新为=旧状态+1,将 当前状态加入到队尾

如果 玩家 的新位置是合法的,没有 移动到箱子所在的位置,那么就只更新状态,将 当前状态加入到队头

这样做的原因是:

在计算 推了箱子后的状态 之前,必须保证之前 没推箱子的状态 已经全部计算完成,不然可能会出现一种情况,就是箱子推了一圈回到原来位置,但是由于没有把之前的状态全部计算出来,就会误认为绕了一圈的推动次数比什么都不做的推动次数要少,造成最终结果比实际结果偏大。所以前者状态要放在队头,后者状态要放在队尾。

时间复杂度: O ( m 2 × n 2 ) O(m^2 \times n^2) O(m2×n2)

C++代码:

using PII = pair<int,int>;

const int dx[4] = {1,0,-1,0};
const int dy[4] = {0,1,0,-1};

class Solution {
public:
    int minPushBox(vector<vector<char>>& grid) {
        int m = grid.size() , n = grid[0].size();

        int sx = 0,sy = 0,bx = 0,by = 0;
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == 'S'){
                    sx = i , sy = j;
                }
                else if(grid[i][j] == 'B'){
                    bx = i , by = j;
                }
            }
        }

        auto check = [&](int i,int j){
            if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '#') return false;
            return true;
        };

        vector<vector<int>> f(m*n , vector<int> (m*n , 1e9));

        deque<PII> q;
        q.emplace_back(bx * n + by , sx * n + sy);
        f[bx * n + by][sx * n + sy] = 0;

        while(!q.empty()){
            auto [b1,s1] = q.front();
            q.pop_front();
            int sx1 = s1 / n , sy1 = s1 % n , bx1 = b1 / n , by1 = b1 % n;
            
            //箱子已经被推到终点
            if(grid[bx1][by1] == 'T') return f[b1][s1];

            for(int k = 0;k < 4;k++){

                //玩家移动
                int sx2 = sx1 + dx[k] , sy2 = sy1 + dy[k] , s2 = sx2 * n + sy2;

                //判断玩家移动位置是否合法
                if(!check(sx2,sy2)) continue;

                //如果此时玩家的位置恰好在 箱子的位置上,说明箱子被要被推动

                //此时可以推动箱子
                if(sx2 == bx1 && sy2 == by1){

                    int bx2 = bx1 + dx[k] , by2 = by1 + dy[k] , b2 = bx2 * n + by2;

                    //判断移动箱子是否合法
                    if(!check(bx2 , by2) || f[b2][s2] <= f[b1][s1] + 1) continue;
                    f[b2][s2] = f[b1][s1] + 1;
                    q.emplace_back(b2,s2);
                }
                //不能推动箱子,只是移动了玩家位置
                else{
                    if(f[b1][s2] <= f[b1][s1]) continue;

                    f[b1][s2] = f[b1][s1];
                    q.emplace_front(b1,s2);
                }
            }
        }
        return -1;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>