37. 解数独(Sudoku Solver)
频次 ★★★ · 难度 🔴 · 高频:阿里
题目
填充 9×9 数独板,使每行/每列/每个 3×3 宫格填入 1~9 不重复。题目保证有唯一解。
思路
回溯 + 约束传播:用三个 boolean 数组记录每行/每列/每宫格中 19 的使用情况。遍历空格,尝试填入 19 的有效数字并递归;找到第一个可行解即返回(题目保证唯一解)。
代码
public void solveSudoku(char[][] board) {
boolean[][] rows = new boolean[9][10];
boolean[][] cols = new boolean[9][10];
boolean[][] boxes = new boolean[9][10];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int n = board[i][j] - '0';
int b = (i / 3) * 3 + j / 3;
rows[i][n] = cols[j][n] = boxes[b][n] = true;
}
}
}
backtrack(board, 0, 0, rows, cols, boxes);
}
private boolean backtrack(char[][] board, int i, int j,
boolean[][] rows, boolean[][] cols, boolean[][] boxes) {
if (j == 9) { i++; j = 0; } // 换行
if (i == 9) return true; // 所有格子填完
if (board[i][j] != '.') return backtrack(board, i, j + 1, rows, cols, boxes);
int b = (i / 3) * 3 + j / 3;
for (int n = 1; n <= 9; n++) {
if (rows[i][n] || cols[j][n] || boxes[b][n]) continue;
board[i][j] = (char) ('0' + n);
rows[i][n] = cols[j][n] = boxes[b][n] = true;
if (backtrack(board, i, j + 1, rows, cols, boxes)) return true;
rows[i][n] = cols[j][n] = boxes[b][n] = false;
board[i][j] = '.';
}
return false;
}复杂度
- 时间:O(9^m) —— m 为空格数量,实际受约束传播大幅优化
- 空间:O(9×9) —— 三个标记数组 + 递归栈
边界条件
- 已填满的棋盘:直接返回
- 无解:题目保证有解(实际中返回 false 表示无解)
变式
- 36. 有效的数独:只判合法性不求解,同样用三个 boolean 数组
- 舞蹈链(Dancing Links):精确覆盖问题的算法解数独,比回溯快但面试不要求
易错点
(i / 3) * 3 + j / 3是宫格索引:i/3 取行组(02),乘以 3 后 + j/3(列组),得到 08 的宫格编号。很多人临场推不出的公式,最好记牢- 递归返回 boolean 有利于”找到就返回”——N 皇后是收集所有解所以 void,本题只需一个解所以返回 boolean
- 数字转字符:
(char)('0' + n),反向board[i][j] - '0'
面试追问
- 如何进一步优化? 每次选”可选数字最少”的空格先填(MRV 启发式),大幅减少回溯分支。展示对约束传播的理解