LeetCode773.滑动谜题

530 阅读3分钟

这是我参与更文挑战的第17天,活动详情查看:更文挑战

前言:有人举过这样一个例子,先给你张北大的录取通知书,但要求你每天5点起床,12点睡觉😪,刻苦学习,勤奋上进。只要你坚持三年,这张通知书就有效。如果是你,你能坚持吗?其实对于这个例子很难在我们的人生中出现,因为它目标明确,有准确的行军路线。就像你是土豪家庭,家里给你安排的明明白白一样,只要你按照这个方式走就不会有问题。可大多数时候我们并没有这样的路线,甚至不知道多久到达自己的黎明。但!谁又不渴望见到黎明呢,坚持吧!

773.滑动谜题

在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示. 一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换. 最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。 给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。

示例:

输入:board = [[1,2,3],[4,0,5]]
输出:1
解释:交换 051 步完成

输入:board = [[1,2,3],[5,4,0]]
输出:-1
解释:没有办法完成谜板

输入:board = [[3,2,4],[1,5,0]]
输出:14

输入:board = [[4,1,2],[5,0,3]]
输出:5
解释:最少完成谜板的最少移动次数是 5 ,
一种移动路径:
尚未移动: [[4,1,2],[5,0,3]]
移动 1 次: [[4,1,2],[0,5,3]]
移动 2 次: [[0,1,2],[4,5,3]]
移动 3 次: [[1,0,2],[4,5,3]]
移动 4 次: [[1,2,0],[4,5,3]]
移动 5 次: [[1,2,3],[4,5,0]]

方法一

权值为1的最短路问题,首先想到使用广度优先搜索,从初始状态开始搜索,每次将那些可以从队头转移过来的合法状态加入队列。每一个元素出队时,判断是否为题目所要求的答案,如果是,返回。否则,继续搜索。如果一直到队列为空时,仍然还未搜索到答案,则说明该初始状态无法转移到答案,返回-1。

具体细节:将一个二维的类似于棋盘的这种状态用一个一维的字符串来表示;题中规定只能移动0的上下左右,上-3,下+3,左-1,右+1;用一个哈希表来记录哪些状态是已经访问过的,以防止再次进入队列,而造成一个无限的循环。哈希表存的是一个键值对,key是状态,value是从初始状态到key状态需要走的最少步数。

    class Solution {
    public:
        typedef pair<string, int> PSI;
        int slidingPuzzle(vector<vector<int>>& board) {
            unordered_map<string, bool> st; //哈希表
            int dx[4] = {-3, 3, 1, -1}; //四个方法
            string start; //初始状态
            for (int i = 0; i < 2; i ++ )
                for (int j = 0; j < 3; j ++ )
                    start += board[i][j] + '0';
    
            queue<PSI> q; 
            q.push({start, 0});
            st[start] = true;
            while (q.size()) {
                auto t = q.front();
                q.pop(); 
                if (t.first == "123450") return t.second; // 如果是答案,返回
    
                int index = t.first.find('0');
                for (int i = 0; i < 4; i ++ ) { //枚举四个方法
                    int idx = index + dx[i];
                    if (idx == 2 && index == 3 || idx == 3 && index == 2) continue; //判断是否合法
                    if (idx >= 0 && idx < 6) {
                        string temp = t.first;
                        swap(temp[index], temp[index + dx[i]]);
                        if (!st[temp]) { //合法且判断之前有无入队过
                            st[temp] = true;
                            q.push({temp, t.second + 1});
                        }
                    }
                }
            }
            return -1;
        }
    };

注意: 在计算转移状态是否合法时,按照上述代码要加上if (idx == 2 && index == 3 || idx == 3 && index == 2) continue;这行,这是排除第一行的最后一列和第二行的第一列发生交换,这一种非法的情况。

复杂度

时间复杂度:O((nm)!4nm),n*m的棋盘有O((nm)!)种情况,每种情况需要找到0所在的位置,并且遍历它的四个方法O(4nm)。

空间复杂度:O((nm)!nm),最多需要在队列中存(nm)!个长度为nm的字符串。