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]
.
看到這道題不禁讓博主想起了文曲星上的游戲-華容道,好吧,又暴露年齡了|||-.-,貌似文曲星這種電子辭典神馬的已經是很古老的東西了,但是上面的一些經典游戲,什么英雄壇說啊,華容道啊,雖然像素分辨率低的可憐,畫面效果連小霸王學習機其樂無窮都比不上,更不要說跟現在的什么擼啊擼,吃雞之類的畫面相比了,但是卻給初高中時代的博主學習之余帶來了無限的樂趣。不過這題跟華容道還是有些不同的,因為那個游戲各塊的大小不同,而這道題的拼圖大小都是一樣的。那么像這種類似迷宮遍歷的問題,又要求最小值的問題,要有強烈的BFS的感覺,沒錯,這道題正是用BFS來解的。這道題好就好在限定了棋盤的大小,是2x3的,這就讓題目簡單了許多,由於0的位置只有6個,我們可以列舉出所有其下一步可能移動到的位置。為了知道每次移動后拼圖是否已經恢復了正確的位置,我們肯定需要用個常量表示出正確位置以作為比較,那么對於這個正確的位置,我們還用二維數組表示嗎?也不是不行,但我們可以更加簡潔一些,就用一個字符串 "123450"來表示就行了,注意這里我們是把第二行直接拼接到第一行后面的,數字3和4起始並不是相連的。好,下面來看0在不同位置上能去的地方,字符串長度為6,則其坐標為 012345,轉回二維數組為:
0 1 2 3 4 5
那么當0在位置0時,其可以移動到右邊和下邊,即{1, 3}位置;在位置1時,其可以移動到左邊,右邊和下邊,即{0, 2, 4}位置;在位置2時,其可以移動到左邊和下邊,即{1, 5}位置;在位置3時,其可以移動到上邊和右邊,即{0, 4}位置;在位置4時,其可以移動到左邊,右邊和上邊,即{1, 3, 5}位置;在位置5時,其可以移動到上邊和左邊,即{2, 4}位置。
然后就是標准的BFS的流程了,使用一個HashSet來記錄訪問過的狀態,將初始狀態start放入,使用一個queue開始遍歷,將初始狀態start放入。然后就是按層遍歷,取出隊首狀態,先和target比較,相同就直接返回步數,否則就找出當前狀態中0的位置,到dirs中去找下一個能去的位置,賦值一個臨時變量cand,去交換0和其下一個位置,生成一個新的狀態,如果這個狀態不在visited中,則加入visited,並且壓入隊列queue,步數自增1。如果while循環退出后都沒有回到正確狀態,則返回-1,參見代碼如下:
解法一:
class Solution { public: int slidingPuzzle(vector<vector<int>>& board) { int res = 0, m = board.size(), n = board[0].size(); string target = "123450", start = ""; vector<vector<int>> dirs{{1,3}, {0,2,4}, {1,5}, {0,4}, {1,3,5}, {2,4}}; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { start += to_string(board[i][j]); } } unordered_set<string> visited{start}; queue<string> q{{start}}; while (!q.empty()) { for (int i = q.size() - 1; i >= 0; --i) { string cur = q.front(); q.pop(); if (cur == target) return res; int zero_idx = cur.find("0"); for (int dir : dirs[zero_idx]) { string cand = cur; swap(cand[dir], cand[zero_idx]); if (visited.count(cand)) continue; visited.insert(cand); q.push(cand); } } ++res; } return -1; } };
上面的解法雖然很炫,但是有局限性,比如若棋盤很大的話,難道我們還手動列出所有0能去的位置么?其實我們可以使用最普通的BFS遍歷方式,就檢查上下左右四個方向,那么這樣我們就不能壓縮二維數組成一個字符串了,我們visited數組中只能放二維數組了,同樣的,queue 中也只能放二維數組,由於二維數組要找0的位置的話,還需要遍歷,為了節省遍歷時間,我們將0的位置也放入queue中,那么queue中的放的就是一個pair對兒,保存當前狀態,已經0的位置,初始時將棋盤以及0的位置排入queue中。之后的操作就跟之前的解法沒啥區別了,只不過這里我們的心位置就是上下左右,如果未越界的話,那么和0交換位置,看新狀態是否已經出現過,如果這個狀態不在visited中,則加入visited,並且壓入隊列queue,步數自增1。如果while循環退出后都沒有回到正確狀態,則返回-1,參見代碼如下:
解法二:
class Solution { public: int slidingPuzzle(vector<vector<int>>& board) { int res = 0; set<vector<vector<int>>> visited; queue<pair<vector<vector<int>>, vector<int>>> q; vector<vector<int>> correct{{1, 2, 3}, {4, 5, 0}}; vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 3; ++j) { if (board[i][j] == 0) q.push({board, {i, j}}); } } while (!q.empty()) { for (int i = q.size() - 1; i >= 0; --i) { auto t = q.front().first; auto zero = q.front().second; q.pop(); if (t == correct) return res; visited.insert(t); for (auto dir : dirs) { int x = zero[0] + dir[0], y = zero[1] + dir[1]; if (x < 0 || x >= 2 || y < 0 || y >= 3) continue; vector<vector<int>> cand = t; swap(cand[zero[0]][zero[1]], cand[x][y]); if (visited.count(cand)) continue; q.push({cand, {x, y}}); } } ++res; } return -1; } };
參考資料:
https://leetcode.com/problems/sliding-puzzle/discuss/113613/C++-9-lines-DFS-and-BFS