# [773] 滑动谜题

在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示。一次 移动 定义为选择 0 与一个相邻的数字(上下左右)进行交换.

最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。

给出一个谜板的初始状态 board ,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。

示例 1:

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

输出:1

解释:交换 0 和 5 ,1 步完成

示例 2:

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

输出:-1

解释:没有办法完成谜板

示例 3:

输入: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.length == 2

board[i].length == 3

0 <= board[i][j] <= 5

board[i][j] 中每个值都 不同

这道题看上去像是一道暴力寻路的问题,因此我们首先想到的是 BFS 的方式。但是作为 hard 级别的题目,直接使用 BFS肯定是找不到思路的。

我们可以发现,可以被移动的点只有 '0' 的四周,因此 BFS 的起点就找到了。以0值所在的点为起点,其上下左右进行 BFS 检索,被扩展一步,步数 + 1。

那么 visited 数组该如何定义呢?

visited 数组本意上是对每种情况做一个缓存判断,若该种情况有计算过,则直接跳过,否则将该种情况加入到下一个检索队列中。

那么,本题的每种情况即一整个棋面,那么我们只需要序列化这个二维数组,作为该种情况的key即可。

同理,二维数组的棋面也可以序列化为一维字符串。但还需要保留之前二维数组的上下左右的邻接关系。因为棋盘规格确定为了 2*3,因此直接写出构造每一个节点的邻接节点索引即可。

该邻接节点索引表即为之后 BFS 的遍历情况。

function slidingPuzzle(board: number[][]): number {
  const rowLen = board.length;
  const colLen = board[0].length;

  const target = '123450';
  // 将二维拍扁成一维,并构造为字符串,方便作为该种情况的 key
  let start = '';
  for (let i = 0; i < rowLen; i++) {
    for (let j = 0; j < colLen; j++) {
      start += board[i][j];
    }
  }
  // 构造每一个节点的邻接节点索引
  const neighbors: number[][] = [
    [1, 3],
    [0, 2, 4],
    [1, 5],
    [0, 4],
    [1, 3, 5],
    [2, 4]
  ];

  const queue: string[] = [start];
  const visited: Set<string> = new Set([start]);

  let step = 0;
  while (queue.length) {
    let len = queue.length;
    while (len--) {
      const curr = queue.pop();
      if (curr === target) {
        return step;
      }
      // 找到数字为0的索引
      let index = curr.split('').findIndex(ch => ch === '0');

      for (const neighborIndex of neighbors[index]) {
        const swaped = swap(neighborIndex, index, curr);
        if (!visited.has(swaped)) {
          queue.unshift(swaped);
          visited.add(swaped);
        }
      }
    }
    step += 1;
  }
  return -1;

  function swap(indexA: number, indexB: number, s: string) {
    const tempA = s[indexA];
    const tempB = s[indexB];
    const sArr = s.split('');
    sArr[indexA] = tempB;
    sArr[indexB] = tempA;
    return sArr.join('');
  }
}