跳到主要内容

37.解数独

链接:37.解数独
难度:Hard
标签:数组、哈希表、回溯、矩阵
简介:编写一个程序,通过已填充的空格来解决数独问题。

题解 1 - typescript

  • 编辑时间:2020-09-15
  • 执行用时:96ms
  • 内存消耗:38.4MB
  • 编程语言:typescript
  • 解法介绍:[参考连接](https://leetcode-cn.com/problems/sudoku-solver/solution/di-gui-hui-su-wei-yun-suan-by-zoffer-3/)。
class Sudoku {
private rows = new Array(9).fill(0);
private cols = new Array(9).fill(0);
private boxs = Array.from({ length: 3 }, () => new Array(3).fill(0));
private emptyCells = new Set<number>();
constructor(private board: string[][]) {}
solve() {
//初始化已知的数字
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
const num = this.board[i][j];
if (num !== '.') {
//将数字转化为二进制标记
//1 -> 0b1, 2 -> 0b10, 3 -> 0b100, 4 -> 0b1000 ...
const sign = 1 << (Number(num) - 1);
this.rows[i] |= sign;
this.cols[j] |= sign;
this.boxs[Math.floor(i / 3)][Math.floor(j / 3)] |= sign;
} else {
this.emptyCells.add((i << 4) | j);
}
}
}
//主逻辑
return this.fillNext();
}
fillNext() {
let cellInfo = this.getEmptyCell();
if (cellInfo === null) {
//没有空格,解题成功
return true;
}
let [i, j, possible] = cellInfo;
while (possible) {
//截取其中一个可能性
const sign = -possible & possible;
//填入空格
this.fillCell(i, j, sign);
//继续下一个填充
if (this.fillNext()) {
//填充成功
return true;
} else {
//排除当前数字
possible ^= sign;
//清空空格
this.cleanCell(i, j, sign);
}
}
//穷尽所有可能性,回溯
return false;
}
getEmptyCell() {
let min = 10;
let cellInfo = null;
for (const id of this.emptyCells) {
const i = id >> 4,
j = id & 0b1111;
const possible = this.getCellPossible(i, j);
const count = this.countPossible(possible);
if (min > count) {
//挑选可能性最少的格子,理论上可减少犯错回溯
cellInfo = [i, j, possible];
min = count;
}
}
return cellInfo;
}
countPossible(possible: number) {
//计算二进制 1 的数量
let count = 0;
while (possible) {
possible &= possible - 1;
count++;
}
return count;
}
fillCell(i: number, j: number, sign: number) {
//对应位变成1,标记占用
this.rows[i] |= sign;
this.cols[j] |= sign;
this.boxs[Math.floor(i / 3)][Math.floor(j / 3)] |= sign;
//填入空格
this.emptyCells.delete((i << 4) | j);
this.board[i][j] = String(Math.log2(sign) + 1);
}
cleanCell(i: number, j: number, sign: number) {
//对应位变为0,清除占用
this.rows[i] &= ~sign;
this.cols[j] &= ~sign;
this.boxs[Math.floor(i / 3)][Math.floor(j / 3)] &= ~sign;
//清空格子
this.emptyCells.add((i << 4) | j);
this.board[i][j] = '.';
}
getCellPossible(i: number, j: number) {
//获取格子可能的取值,二进制1表示可选
return (
(this.rows[i] | this.cols[j] | this.boxs[Math.floor(i / 3)][Math.floor(j / 3)]) ^ 0b111111111
);
}
}
function solveSudoku(board: string[][]): void {
new Sudoku(board).solve();
}