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:
1.BFS算法(引用自:labuladong)
对于这种计算最小步数的问题,我们就要敏感地想到 BFS 算法。
这个题目转化成 BFS 问题是有一些技巧的,我们面临如下问题:
1、一般的 BFS 算法,是从一个起点start开始,向终点target进行寻路,但是拼图问题不是在寻路,而是在不断交换数字,这应该怎么转化成 BFS 算法问题呢?
2、即便这个问题能够转化成 BFS 问题,如何处理起点start和终点target?
首先回答第一个问题,BFS 算法并不只是一个寻路算法,而是一种暴力搜索算法,只要涉及暴力穷举的问题,BFS 就可以用,而且可以最快地找到答案。明白了这个道理,我们的问题就转化成了:如何穷举出board当前局面下可能衍生出的所有局面?这就简单了,看数字 0 的位置呗,和上下左右的数字进行交换就行了:

这样其实就是一个 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]:

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

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

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                start += 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<String> q = new LinkedList<>();
        //防止走回头路,这里最好用String而不是StringBuilder,否则判断contains时会对比不出来
        Set<String> visited = new HashSet<>();
        q.add(start);
        visited.add(start);

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

                //对0位置的每个邻居交换
                for (int adj : neighbor.get(idx)) {
                    //swap adj和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_n_board);
                        visited.add(new_n_board);
                    }
                }
            }
            step++;
        }
        return -1;
    }

2.A*搜索算法
思路:基于普通的广度优先遍历解法, 将普通队列改为使用优先队列, 每次队列poll时都先通过compareTo选择一个"曼哈顿距离最短", 也就是距离最终结果需要交换次数最小的一个分支进行计算。像这样每次队列都择优poll, BFS算法就能够更快速的到达终点。
关于曼哈顿距离:二维坐标中, 一个点(x1, y1)到另一个点(x2, y2)的曼哈顿距离 = |x1 - x2| + |y1 - y2|
由于优先队列默认队列头元素是最小元素,故我们可以拿来作为小根堆使用, 故这个A* 算法的主要思想就是每次都poll一个交换次数 + 曼哈顿距离最短的节点, 从而达到枝减的效果

    public int slidingPuzzle(int[][] board) {
        int[][] dir = {{1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4}};
        int[] endBoard = {1, 2, 3, 4, 5, 0};
        ANode curr = new ANode(board);
        if (Arrays.equals(curr.board, endBoard))
            return 0;

        PriorityQueue<ANode> queue = new PriorityQueue<>();
        queue.offer(curr);
        HashSet<ANode> visited = new HashSet<>();
        visited.add(curr);

        while (!queue.isEmpty()) {
            curr = queue.poll();
            for (int nextZeroPos : dir[curr.zeroPos]) {
                int[] nextBoard = Arrays.copyOf(curr.board, 6);
                swap(nextBoard, curr.zeroPos, nextZeroPos);
                if (Arrays.equals(nextBoard, endBoard))
                    return curr.count + 1;
                ANode next = new ANode(nextBoard, nextZeroPos, curr.count + 1);
                if (visited.contains(next))
                    continue;
                queue.offer(next);
                visited.add(next);
            }
        }
        return -1;
    }
原文地址:https://www.cnblogs.com/xym4869/p/13071645.html