# 037 -- 解数独

# 题目

题目

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。

  • 示例1
输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: 数独的解
[
  ["5", "3", "4", "6", "7", "8", "9", "1", "2"],
  ["6", "7", "2", "1", "9", "5", "3", "4", "8"],
  ["1", "9", "8", "3", "4", "2", "5", "6", "7"],
  ["8", "5", "9", "7", "6", "1", "4", "2", "3"],
  ["4", "2", "6", "8", "5", "3", "7", "9", "1"],
  ["7", "1", "3", "9", "2", "4", "8", "5", "6"],
  ["9", "6", "1", "5", "3", "7", "2", "8", "4"],
  ["2", "8", "7", "4", "1", "9", "6", "3", "5"],
  ["3", "4", "5", "2", "8", "6", "1", "7", "9"]
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 解析 解法说明

提示

🔊 利用回溯算法来解决数独问题

# 回溯算法

回溯算法,实际上就是一个决策树的遍历过程。顾名思义,回溯就是在不满足当前条件时,回退到上一个节点继续验证下一个可能解,直至所有的 你只需要思考 3 个问题:

  • 1、路径:也就是已经做出的选择。

  • 2、选择列表:也就是你当前可以做的选择。

  • 3、结束条件:也就是到达决策树底层,无法再做选择的条件。

回溯算法伪代码如下:[重要]

let result = [];
function backtrack (路径, 选择, ...args) {
  if (满足条件) {
    result.push(路径)
    return
  }
  for (循环选择项) {
    做出选择;
    backtrack(路径,选择, ...args);
    撤销选择;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

回溯算法经典的题目是全排列和n皇后问题,下面分析全排列问题。 全排列,就是列举出给定数组的全部排列方式, 例如:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
1
2
3
4
5
6
7
8
9
10

套用回溯框架题解源码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function (nums) {
  let result = [];
  let tempList = [];
  backtrack(result, tempList, nums);
  return result;
};
// 回溯算法
function backtrack(result, tempList, nums) {
  // 得到一个结果
  if (tempList.length === nums.length) {
    return result.push([...tempList]);
  }
  // 回溯条件
  for (let i = 0; i < nums.length; i++) {
    // 筛选回溯条件
    if (tempList.indexOf(nums[i]) > -1) continue;
    // 做出回溯选择
    tempList.push(nums[i]);
    // 回溯
    backtrack(result, tempList, nums);
    // 不符合条件,撤销选择
    tempList.pop();
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 题解源码

数独求解提供两种解法,解法1在寻找到第一个满足的解时就会返回,而解法二会求解全部的数独解,将全部的解存在res中。

需要注意回溯停止条件和筛选符合条件的元素

# 解法1

/**
 * @description 寻找到了第一个解即返回
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function (board) {
  backtrack(board, 0, 0);
  return board;

  function backtrack(board, i, j) {
    let m = 9,
      n = 9;
    if (i === m) {
      return true;
    }
    if (j === n) {
      return backtrack(board, i + 1, 0)
    }
    if (board[i][j] !== '.') {
      return backtrack(board, i, j + 1);
    }
    for (let k = 1; k <= 9; k++) {
      if (!isValid(board, i, j, k.toString())) continue;

      board[i][j] = k.toString();
      if (backtrack(board, i, j + 1)) {
        return true;
      }
      board[i][j] = '.'
    }
    return false;
  }

  function isValid(board, i, j, str) {
    for (let k = 0; k < 9; k++) {
      // 判断横行没有重复
      if (board[i][k] === str) return false;
      if (board[k][j] === str) return false;
      if (
        board[
          Math.floor(i / 3) * 3 + Math.floor(k / 3)
        ][
          Math.floor(j / 3) * 3 + k % 3
        ] === str
      ) {
        return false
      };
    }
    return true;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 解法2

/**
 * @description 利用标准回溯算法
 * @param {character[][]} board
 * @return {void}
 */
var solveSudokuElse = function (board) {
  let res = [];
  backtrack(board, 0, 0);
  return res;

  function backtrack(board, i, j) {
    let m = 9,
      n = 9;
    // 说明已经找到一个解,将解压入结果中
    if (i === m) {
      return res.push(board);
    }
    // 说明一行已经找到解,进行下一行
    if (j === n) {
      backtrack(extend(board), i + 1, 0)
      return;
    }
    // 跳过已经填写了数字的位置
    if (board[i][j] !== '.') {
      backtrack(extend(board), i, j + 1);
      return;
    }
    // 标准回溯算法
    for (let k = 1; k <= 9; k++) {
      // 不符合条件的筛掉
      if (!isValid(board, i, j, k.toString())) continue;
      board[i][j] = k.toString();
      backtrack(extend(board), i, j + 1)
      board[i][j] = '.'
    }
  }
  // 深拷贝
  function extend(source) {
    let target = null;
    if (typeof source === 'object') {
      target = Array.isArray(source) ? [] : {}
      for (let key in source) {
        if (source.hasOwnProperty(key)) {
          if (typeof source[key] !== 'object') {
            target[key] = source[key]
          } else {
            target[key] = extend(source[key])
          }
        }
      }
    } else {
      target = source
    }
    return target
  }

  function isValid(board, i, j, str) {
    for (let k = 0; k < 9; k++) {
      // 判断横行没有重复
      if (board[i][k] === str) return false;
      if (board[k][j] === str) return false;
      if (
        board[
          Math.floor(i / 3) * 3 + Math.floor(k / 3)
        ][
          Math.floor(j / 3) * 3 + k % 3
        ] === str
      ) {
        return false
      };
    }
    return true;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
上次更新: 5/17/2020, 8:56:09 AM