1. 程式人生 > >Leetcode 773. Sliding Puzzle

Leetcode 773. Sliding Puzzle

題目

On a 2x3 board, there are 5 tiles represented by the integers 1 through 5, and an empty square represented by 0.

A move consists of choosing 0 and a 4-directionally adjacent number and swapping it.

The state of the board is solved if and only if the board is [[1,2,3],[4,5,0]].

Given a puzzle board, return the least number of moves required so that the state of the board is solved. If it is impossible for the state of the board to be solved, return -1.

Examples:

Input: board = [[1,2,3],[4,0,5]] Output: 1 Explanation: Swap the 0 and the 5 in one move.

Input: board = [[1,2,3],[5,4,0]] Output: -1 Explanation: No number of moves will make the board solved.

Input: board = [[4,1,2],[5,0,3]] Output: 5 Explanation: 5 is the smallest number of moves that solves the board. An example path: After move 0: [[4,1,2],[5,0,3]] After move 1: [[4,1,2],[0,5,3]] After move 2: [[0,1,2],[4,5,3]] After move 3: [[1,0,2],[4,5,3]] After move 4: [[1,2,0],[4,5,3]] After move 5: [[1,2,3],[4,5,0]]

Input: board = [[3,2,4],[1,5,0]] Output: 14

Note:

  • board will be a 2 x 3 array as described above.
  • board[i][j] will be a permutation of [0, 1, 2, 3, 4, 5].

Solution

class Solution {
public:
    int slidingPuzzle(vector<vector<int>>& board) {
        string init = to_string(board[0][0]) + to_string(board[0][1]) + to_string(board[0][2])
                       + to_string(board[1][0]) + to_string(board[1][1]) + to_string(board[1][2]);
        
        vector<vector<int>> movePos{{1,3},{0,2,4},{1,5},{0,4},{3,5,1},{4,2}};
        queue<string> solutionStates;
        unordered_set<string> record;
        record.insert(init);
        solutionStates.push(init);
        int depth = 0;
        while (!solutionStates.empty()) {
            int size = solutionStates.size();
            while (size--) {
                string currentSolution = solutionStates.front();
                if (currentSolution == "123450") return depth;

                int zeroPosition = (int) currentSolution.find("0");
                for (auto nextPos : movePos[zeroPosition]) {
                    std::swap(currentSolution[zeroPosition], currentSolution[nextPos]);
                    if (record.find(currentSolution) == record.end()) {
                        record.insert(currentSolution);
                        solutionStates.push(currentSolution);
                    }
                    std::swap(currentSolution[zeroPosition], currentSolution[nextPos]);
                }
                solutionStates.pop();
            }
            depth++;
        }
        return -1;
    }
};

分析

這道題寫的好累= =巨tm多東西

首先我們可以發現這道題和八數碼問題很像,八數碼是移動空白格,而本題是移動0所在的格。因此馬上可以想到是用BFS的(當然哪位讀者猜不到也沒什麼關係因為這題是我根據BFS的tag找到的所以我肯定是能想到BFS的)。

我們將當前board的每個狀態視作BFS的一個節點,根據0所在的位置,這個節點可以發展出2個或者3個不同的下線,同樣也是board的狀態。因此我們經營BFS的佇列(queue),注意的是要每次記錄當前節點的深度depth。除此以外,由於儲存為vector<vector<int>>的儲存代價比較大,因此我們選擇儲存為string的形式,可以有效地降低記憶體消耗。

使用movePos記錄0在每個位置時,可以發生swap的位置(在string中),進一步降低了開銷。

再然後,使用了record記錄了已經過BFS遍歷的節點,儲存起來防止多次進行重複的BFS。同時這也使得本演算法可以斷定某些情況沒有解。

這裡還有一些奇奇怪怪的優化,比如recordunordered_set,要比使用set或者其他什麼資料結構要快得多;又比如movePosvector<vector<int>>要比用map<int, vector<int>>還要快一些。