# [37] 解数独
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。
Note:
给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。
数独问题,每一个格子都有1-9可以选,那最暴力的方法就是,对每一个格子都从1-9开始试,知道试出一个解为止。
之后我们再确定递归顺序,我们可以以行遍历,即从左到右,从上到下开始依次填空。
那么当遍历到一列结尾时,下一个遍历点是下一行的第一个格子;
当遍历到行越界时,说明整个格子已经被填完,找到了一个解,返回;
当格子中已经有预设的值时,跳过该格子,从下一个格子开始继续遍历;
那么,如何判断当前格子能填入某数字呢?很简单,判断行、列、九宫格内没有要填入的相同数字即为合法。
之后,就是回溯问题的普遍思路了:做选择、递归、取消选择。
function solveSudoku(board: string[][]): void {
// board 为正方形,长宽相等
const size = board.length;
backtrack(0, 0);
function backtrack(row: number, col: number): boolean {
// 如果递归到行越界,说明已经把图遍历完成了,找到了一组完全可行的解
if (row === size) return true;
// 如果递归到某列结尾,将进入下一行的开头继续递归
if (col === size) return backtrack(row + 1, 0);
// 若此处已经有填入值,则跳过去找下一个空值点
if (board[row][col] !== '.') {
return backtrack(row, col + 1);
}
for (let i = 1; i <= 9; i++) {
// 看此处是否满足数独条件,不满足直接跳过
if (!isValid(row, col, String(i))) continue;
// 做选择
board[row][col] = String(i);
// 递归
const flag = backtrack(row, col + 1);
// 发现一个解,剩余情况全部剪枝
if (flag) return true;
// 取消选择
board[row][col] = '.';
}
return false;
}
function isValid(row: number, col: number, ch: string) {
for (let i = 0; i < size; i++) {
// 横行不能有相同字符
if (board[row][i] === ch) return false;
// 竖行不能有相同字符
if (board[i][col] === ch) return false;
}
// 九宫格不能有相同字符
const rowStart = Math.floor(row / 3) * 3;
const colStart = Math.floor(col / 3) * 3;
for (let i = rowStart; i < rowStart + 3; i++) {
for (let j = colStart; j < colStart + 3; j++) {
if (board[i][j] === ch) return false;
}
}
return true;
}
}