【算法】Minimum Moves to Move a Box to Their Target Location 推箱子

文章目录

  • Minimum Moves to Move a Box to Their Target Location 推箱子
    • 问题描述:
    • 分析
    • 代码
  • Tag

Minimum Moves to Move a Box to Their Target Location 推箱子

问题描述:

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

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

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

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

地图是一个矩阵,MN,M,N的范围[1,20]

分析

对于人类来说,简单的关卡可以通过肉眼直接判断结果,但是对于复杂的关卡,仅依赖直观是无法判断的。这个游戏很多老的PDA上面都有。
这个游戏是推箱子,而不是拉箱子,所以箱子只能以一种方式移动。箱子最终的目的地一定是target坐标,或者永远无法抵达。
因为最终要求计算推动次数的最小值,而推动是以箱子移动为标准。
如果从其他坐标移动到坐标B,那么就会有4个方向,上下左右。所以对于human来说,也会有一个状态,之前是向上,为了使箱子移动到B,human的新坐标就是之前箱子的坐标。抽象的来说,箱子到达坐标B,一定会有4个状态,human也一样。
问题在于这个状态,如何表示和记录。
需要注意的是,human和box的关系,他们不一定是紧靠的,因为在需要换方向的时候,box不变,但是human的坐标是改变的。
即 box的坐标,human坐标,到达该box坐标的step,三者是有关系的。
而最终要算的是box从开始到达最终目的地坐标的最小移动。很明显是一个BFS。
定义一个队列queue,queue中存放的是 box坐标,human坐标组合表示的状态,然后利用数组记录到达该状态时的step,进行更新,就可以得到结果。
但是存在一个问题,BFS流程上会以第一次遇到目的地作为结束,返回结果,但是在这个问题上,出队的一个状态state,由它产生的状态可能是box不变,那么其state也不变,也可能是box移动了,那么其state就变了。如果统一的插入队尾,最终一定是出问题的。
可以使用2个queue来解决,当然也可以使用Deque。
对于一些无效的状态,肯定是不需要入队的。用dp[box] [human]记录状态box-human的最小移动,初始化dp 全部为 INTMAX。
如果dp[boxnew] [humannew]<=dp[boxpre] [humanpre]+1,说明这个new state已经visited。
当然对于boxnew来说,它要合法,不能越界,不能和墙重叠。

时间复杂度 O(M2N2)
空间复杂度: O(M2N2)

代码

class Solution {
   public int minPushBox(char[][] grid) {
        int m = grid.length, n = grid[0].length;
        int sx = -1, sy = -1, bx = -1, by = -1; // 玩家、箱子的初始位置
        for (int x = 0; x < m; x++) {
            for (int y = 0; y < n; y++) {
                if (grid[x][y] == 'S') {
                    sx = x;
                    sy = y;
                } else if (grid[x][y] == 'B') {
                    bx = x;
                    by = y;
                }
            }
        }

        int[] d = {0, -1, 0, 1, 0};

        int[][] dp = new int[m * n][m * n];
        for (int i = 0; i < m * n; i++) {
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }
        Queue<int[]> queue = new ArrayDeque<int[]>();
        dp[sx * n + sy][bx * n + by] = 0; // 初始状态的推动次数为 0
        queue.offer(new int[]{sx * n + sy, bx * n + by});
        while (!queue.isEmpty()) {
            Queue<int[]> queue1 = new ArrayDeque<int[]>();
            while (!queue.isEmpty()) {
                int[] arr = queue.poll();
                int s1 = arr[0], b1 = arr[1];
                int sx1 = s1 / n, sy1 = s1 % n, bx1 = b1 / n, by1 = b1 % n;
                if (grid[bx1][by1] == 'T') { // 箱子已被推到目标处
                    return dp[s1][b1];
                }
                for (int i = 0; i < 4; i++) { // 玩家向四个方向移动到另一个状态
                    int sx2 = sx1 + d[i], sy2 = sy1 + d[i + 1], s2 = sx2*n+sy2;
                    if (!ok(grid, m, n, sx2, sy2)) { // 玩家位置不合法
                        continue;
                    }
                    if (bx1 == sx2 && by1 == sy2) { // 推动箱子
                        int bx2 = bx1 + d[i], by2 = by1 + d[i + 1], b2 = bx2*n+by2;
                        if (!ok(grid, m, n, bx2, by2) || dp[s2][b2] <= dp[s1][b1] + 1) { // 箱子位置不合法 或 状态已访问
                            continue;
                        }
                        dp[s2][b2] = dp[s1][b1] + 1;
                        queue1.offer(new int[]{s2, b2});
                    } else {
                        if (dp[s2][b1] <= dp[s1][b1]) { // 状态已访问
                            continue;
                        }
                        dp[s2][b1] = dp[s1][b1];
                        queue.offer(new int[]{s2, b1});
                    }
                }
            }
            queue = queue1;
        }
        return -1;
    }

    public boolean ok(char[][] grid, int m, int n, int x, int y) { // 不越界且不在墙上
        return x >= 0 && x < m && y >= 0 && y < n && grid[x][y] != '#';
    }
} 

代码来源于官解

Tag

BFS Dijkstra

你可能感兴趣的:(数据结构与算法,算法,数据结构,bfs)