1. 程式人生 > 其它 >[LeetCode] 1263. Minimum Moves to Move a Box to Their Target Location 推箱子

[LeetCode] 1263. Minimum Moves to Move a Box to Their Target Location 推箱子


A storekeeper is a game in which the player pushes boxes around in a warehouse trying to get them to target locations.

The game is represented by anm x ngrid of charactersgridwhere each element is a wall, floor, or box.

Your task is to move the box'B'to the target position'T'under the following rules:

  • The character'S'represents the player. The player can move up, down, left, right ingridif it is a floor (empty cell).
  • The character'.'represents the floor which means a free cell to walk.
  • The character'#'represents the wall which means an obstacle (impossible to walk there).
  • There is only one box'B'
    and one target cell'T'in thegrid.
  • The box can be moved to an adjacent free cell by standing next to the box and then moving in the direction of the box. This is apush.
  • The player cannot walk through the box.

Returnthe minimum number ofpushesto move the box to the target. If there is no way to reach the target, return-1

.

Example 1:

Input: grid = [["#","#","#","#","#","#"],
               ["#","T","#","#","#","#"],
              ["#",".",".","B",".","#"],
              ["#",".","#","#",".","#"],
              ["#",".",".",".","S","#"],
              ["#","#","#","#","#","#"]]
Output: 3
Explanation: We return only the number of times the box is pushed.

Example 2:

Input: grid = [["#","#","#","#","#","#"],
               ["#","T","#","#","#","#"],
              ["#",".",".","B",".","#"],
              ["#","#","#","#",".","#"],
              ["#",".",".",".","S","#"],
              ["#","#","#","#","#","#"]]
Output: -1

Example 3:

Input: grid = [["#","#","#","#","#","#"],
              ["#","T",".",".","#","#"],
              ["#",".","#","B",".","#"],
              ["#",".",".",".",".","#"],
              ["#",".",".",".","S","#"],
              ["#","#","#","#","#","#"]]
Output: 5
Explanation:  push the box down, left, left, up and up.

Example 4:

Input: grid = [["#","#","#","#","#","#","#"],
              ["#","S","#",".","B","T","#"],
              ["#","#","#","#","#","#","#"]]
Output: -1

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 20
  • gridcontains only characters'.','#','S','T', or'B'.
  • There is only one character'S','B', and'T'in thegrid.

這道題給了一個二維陣列 grid,代表一個迷宮,裡面有個人在推箱子,人的位置用字母S表示,箱子位置是B,需要將其推到位置T,迷宮中的點表示可以通行的區域,井號#表示牆,即不能通行的位置,問最少需要推幾次才能把箱子推到目標位置。如果沒有仔細讀題目的話,很可能就把這題當成了一道普通的迷宮遍歷問題了,但這是一道 Hard 題目,肯定不是隨隨便便一個 BFS 就能打發了題目。想想為什麼要引入人推箱子這個設定,而且題目中還強調了人不能穿過箱子。箱子要移動,必須要有人去推才行,比如箱子要向左移動一格,則這個人一定要在箱子的右邊,若接下來箱子想向下移動,則人一定要到箱子的上邊,此時需要滿足兩個條件,一個是箱子的上邊一定要是空著的,不能是障礙物,另一個是人必須能夠到達上邊的位置,也就是說即便箱子上方是空的,人若無法走到該位置的話,也是白搭,完全模擬了現實中的推箱子操作。

雖然說存在人推箱子這個設定,但這道題的本質還是迷宮遍歷,求到達目標位置的最小步數還是首選廣度優先遍歷 Breadth-frist Search,不過此時的狀態不再單單是箱子的位置,還應該包括人的位置,二者一起組成 BFS 中的狀態,同時,要進入下一個狀態的前提條件是,人必須要能夠達到指定的推箱子的位置,這個判定就需要一個額外的 BFS 來做了,所以這道題實際需要寫兩個 BFS,是不是感覺很叼~先來寫主 BFS 函式吧,需要用一個 queue 來遍歷,根據前面的分析,每個狀態由箱子和人的位置一起組成,所以 queue 裡放一個 pair 對兒,注意這裡把二維的位置座標 encode 成一個整型數。用一個包含字串的 HashSet 來記錄訪問過的狀態,這裡為了利用 HashSet 的常數級的查詢效率,把狀態的 pair 對兒又 encode 成了一個字串。接下來要先遍歷一遍陣列 grid,找到箱子,人,還有目標點的位置,分別儲存到 box,player,和 target 變數中,然後將這些非井號的字元都標記為點,表示是箱子可以通過的位置。

將箱子的起始位置和人的位置組成 pair 對兒加入 queue 中就可以開始 BFS 遍歷了,由於要求最小步數,所以應該用層序的寫法,在 while 迴圈的內部套一個 for 迴圈(必須寫成初始化為 q.size() 的形式,而不能將其放到中間的判定部分,因為之後 queue 的大小會改變)。取出隊首狀態,得到箱子和人的位置,若此時箱子已經在目標位置了,則直接返回結果 res。否則就接著計算出箱子的橫縱座標,然後開始遍歷其周圍的四個位置,計算出箱子的新位置,同時也要計算出人推箱子需要站的位置,若這兩個位置中任何一個位置越界或者是牆的話,則跳過當前迴圈,否則將箱子和人的新位置 encode 成一個字串,在 HashSet 中查詢,若已經存在了,則跳過,若不存在,還需要用另一個 BFS 來判斷人是否能從之前的位置到達新的位置,把這個 BFS 放到一個單獨的子函式中,就是經典的寫法,這裡就不單獨講了(唯一要注意的是遍歷前要將箱子的位置標記成井號,因為人不能穿過箱子,最後返回結果前要復原)。若 BFS 返回 true 了,再把這個新狀態排入佇列 queue 中,並且加入 HashSet,每層遍歷完之後,結果 res 自增1。若 while 迴圈退出了,說明無法推到目標位置,返回 -1 即可,參見程式碼如下:


class Solution {
public:
    int minPushBox(vector<vector<char>>& grid) {
        int res = 0, m = grid.size(), n = grid[0].size(), start = 0, target = 0, player = 0;
        vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        queue<pair<int, int>> q;
        unordered_set<string> st;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 'B') start = i * n + j;
                else if (grid[i][j] == 'T') target = i * n + j;
                else if (grid[i][j] == 'S') player = i * n + j;
                if (grid[i][j] != '#') grid[i][j] = '.';
            }
        }
        q.push({start, player});
        while (!q.empty()) {
            for (int i = q.size(); i > 0; --i) {
                auto t = q.front(); q.pop();
                int box = t.first, player = t.second;
                if (box == target) return res;
                int xbox = box / n, ybox = box % n;
                for (auto dir : dirs) {
                    int x = xbox + dir[0], y = ybox + dir[1], xp = xbox - dir[0], yp = ybox - dir[1];
                    if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '#') continue;
                    if (xp < 0 || xp >= m || yp < 0 || yp >= n || grid[xp][yp] == '#') continue;
                    string str = to_string(box) + "_" + to_string(xp * n + yp);
                    if (st.count(str)) continue;
                    if (canReach(grid, player, xp * n + yp, box)) {
                        q.push({x * n + y, box});
                        st.insert(str);
                    }
                }
            }
            ++res;
        }
        return -1;
    }
    bool canReach(vector<vector<char>>& grid, int start, int target, int box) {
        int m = grid.size(), n = grid[0].size();
        queue<int> q{{start}};
        vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        vector<bool> visited(m * n);
        visited[start] = true;
        grid[box / n][box % n] = '#';
        while (!q.empty()) {
            int t = q.front(); q.pop();
            if (t == target) {
                grid[box / n][box % n] = '.';
                return true;
            }
            int x0 = t / n, y0 = t % n;
            for (auto &dir : dirs) {
                int x = x0 + dir[0], y = y0 + dir[1];
                if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] != '.' || visited[x * n + y]) continue;
                visited[x * n + y] = true;
                q.push(x * n + y);
            }
        }
        grid[box / n][box % n] = '.';
        return false;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1263


參考資料:

https://leetcode.com/problems/minimum-moves-to-move-a-box-to-their-target-location/

https://leetcode.com/problems/minimum-moves-to-move-a-box-to-their-target-location/discuss/431431/Java-straightforward-BFS-solution

https://leetcode.com/problems/minimum-moves-to-move-a-box-to-their-target-location/discuss/432593/cpp-two-bfs-solution-8ms-beat-100


LeetCode All in One 題目講解彙總(持續更新中...)


喜歡請點贊,疼愛請打賞❤️~.~


微信打賞


Venmo 打賞