[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 an m x n grid of characters grid where 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 in grid if 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 the grid.
  • 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 a push.
  • The player cannot walk through the box.

Return the minimum number of pushes to 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
  • grid contains only characters '.''#''S''T', or 'B'.
  • There is only one character 'S''B', and 'T' in the grid.

這道題給了一個二維數組 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 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM