LeetCode——滑动谜题

Q:在一个 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
解释:交换 0 和 5 ,1 步完成

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

输入: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]]
输入:board = [[3,2,4],[1,5,0]]
输出:14

提示:
board?是一个如上所述的 2 x 3 的数组.
board[i][j]?是一个?[0, 1, 2, 3, 4, 5]?的排列.

A:(引用自:labuladong)
对于这种计算最小步数的问题,我们就要敏感地想到 BFS 算法。
这个题目转化成 BFS 问题是有一些技巧的,我们面临如下问题:
1、一般的 BFS 算法,是从一个起点start开始,向终点target进行寻路,但是拼图问题不是在寻路,而是在不断交换数字,这应该怎么转化成 BFS 算法问题呢?
2、即便这个问题能够转化成 BFS 问题,如何处理起点start和终点target?
首先回答第一个问题,BFS 算法并不只是一个寻路算法,而是一种暴力搜索算法,只要涉及暴力穷举的问题,BFS 就可以用,而且可以最快地找到答案。明白了这个道理,我们的问题就转化成了:如何穷举出board当前局面下可能衍生出的所有局面?这就简单了,看数字 0 的位置呗,和上下左右的数字进行交换就行了:
LeetCode——滑动谜题
这样其实就是一个 BFS 问题,每次先找到数字 0,然后和周围的数字进行交换,形成新的局面加入队列…… 当第一次到达target时,就得到了赢得游戏的最少步数。

对于第二个问题,我们这里的board仅仅是 2x3 的二维数组,所以可以压缩成一个一维字符串。其中比较有技巧性的点在于,二维数组有「上下左右」的概念,压缩成一维后,如何得到某一个索引上下左右的索引?很简单,我们只要手动写出来这个映射就行了:

List<List<Integer>> neighbor = new ArrayList<>();
        neighbor.add(Arrays.asList(1, 3));
        neighbor.add(Arrays.asList(0, 4, 2));
        neighbor.add(Arrays.asList(1, 5));
        neighbor.add(Arrays.asList(0, 4));
        neighbor.add(Arrays.asList(3, 1, 5));
        neighbor.add(Arrays.asList(4, 2));

这个含义就是,在一维字符串中,索引i在二维数组中的的相邻索引为neighbor[i]:
LeetCode——滑动谜题

至此,我们就把这个问题完全转化成标准的 BFS 问题了。

public int slidingPuzzle(int[][] board) {
        int m = 2, n = 3;
        StringBuilder start = new StringBuilder();
        String target = "123450";

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                start.append(board[i][j]);
            }
        }

        //每个位置的邻居位置
        List<List<Integer>> neighbor = new ArrayList<>();
        neighbor.add(Arrays.asList(1, 3));
        neighbor.add(Arrays.asList(0, 4, 2));
        neighbor.add(Arrays.asList(1, 5));
        neighbor.add(Arrays.asList(0, 4));
        neighbor.add(Arrays.asList(3, 1, 5));
        neighbor.add(Arrays.asList(4, 2));

        Queue<StringBuilder> q = new LinkedList<>();
        //防止走回头路
        Set<String> visited = new HashSet<>();
        q.add(start);
        visited.add(start.toString());

        int step = 0;
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                StringBuilder cur = q.poll();
                String curr = cur.toString();
                if (curr.equals(target)) {
                    return step;
                }
                //找0的位置
                int idx = 0;
                for (; cur.charAt(idx) != ‘0‘; idx++) ;

                //对0位置的每个邻居交换
                for (int adj : neighbor.get(idx)) {
                    char[] new_board = curr.toCharArray();
                    char temp = new_board[adj];
                    new_board[adj] = new_board[idx];
                    new_board[idx] = temp;
                    String new_n_board = new String(new_board);
                    //没走过的路加入
                    if (!visited.contains(new_n_board)) {
                        q.add(new StringBuilder(new_n_board));
                        visited.add(new_n_board);
                    }
                }
            }
            step++;
        }
        return -1;
    }

相关推荐