打卡Leetcode每日一题 37. 解数独。
题目
编写一个程序,通过已填充的空格来解决数独问题。一个数独的解法需要遵循如下规则:
- 数字
1-9
在每一行只能出现一次; - 数字
1-9
在每一列只能出现一次; - 数字
1-9
在每一个以粗实线分隔的3×3
宫内只能出现一次。
空白格用.
表示。
思路
根据题干中给出的3条规则,容易想到采用状态数组记录数字1-9
在每行、每列以及每个九宫格内是否出现以及出现的位置。
-
采用
boolean[][] row
表示列行状态数组,用row[i][num]
表示数字num
出现在第i
行。比如row[0][4] = true
表示数字5(实际数字范围是1-9
)出现在第0行。 -
采用
boolean[][] col
表示列状态数组,用col[j][num]
表示数字num
出现在第j
行。比如col[0][5] = true
表示数字6出现在第0列。 -
采用
boolean[][] block
表示九宫格状态数组。粗实线将整个数独划分成了9
个3×3
九宫格,因此按从左至右、从上至下的顺序将每个九宫格编号为0-8
,用block[blockIndex][num]
表示数字num
出现在第blockIndex
九宫格。比如block[2][5]
表示数字6出现在第2号九宫格。接下来的问题在于如何根据行下标i
和列下标j
计算出九宫格的标号blockIndex
。给出如下计算公式:blockIndex = i / 3 * 3 + j / 3
根据初始状态确定行、列和九宫格状态数组后,采用递归和回溯思想解数独。
-
找到需要填数字的位置,即
board[][] == '.'
的位置; -
在每一个需要填数字的位置
(i,j)
填入数字num
,必须满足该数字没有在当前位置所在的行、列和九宫格中出现,即!row[i][num] && !col[j][num] && !block[blockIndex][num]
为真; -
满足条件后将数字
num
填入,并更新该位置对应的状态数组中的值为true
; -
更新行坐标
i
和列坐标j
,递归求解下一个需要填数字的位置;-
如果求解成功,继续递归;
-
如果求解失败,需要回溯,将当前位置的对应的状态数组的值均设置为
false
,同时将数独中该位置的值重新设置为.
。
-
-
递归结束条件:整个数独遍历完成。
Java实现
求解数独类:
class Solution {
/**
* 解数独
* @param board
*/
public void solveSudoku(char[][] board) {
// 行状态数组
boolean[][] row = new boolean[9][9];
// 列状态数组
boolean[][] col = new boolean[9][9];
// 九宫格状态数组
boolean[][] block = new boolean[9][9];
// 记录数独的初始状态
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int num = board[i][j] - '1';
row[i][num] = true;
col[j][num] = true;
block[i / 3 * 3 + j / 3][num] = true;
}
}
}
// 解数独
dfs(board, row, col, block, 0, 0);
}
/**
* 递归+回溯
* @param board
* @param row
* @param col
* @param block
* @param i
* @param j
* @return
*/
private boolean dfs(char[][] board, boolean[][] row, boolean[][] col, boolean[][] block, int i, int j) {
// 遍历数独每个需要填数字的位置
while (board[i][j] != '.') {
// 对数独的每个位置进行遍历
// 逐行遍历:若列坐标j大于等于9表示一行到头
if (++j >= 9) {
// 行坐标自增跳转下一行
i++;
// 列坐标归零从第一列开始
j = 0;
}
// 当行坐标i大于等于9表示数独遍历完毕
if (i >= 9) return true;
}
// 在需要填数字的位置填入数字
for (int num = 0; num < 9; num++) {
// 确定九宫格状态数组的位置坐标
int blockIndex = i / 3 * 3 + j / 3;
// 所填数字num未在该行、该列以及该九宫格出现时
if (!row[i][num] && !col[j][num] && !block[blockIndex][num]) {
// 将num填入board[i][j]
// 由于num从0开始因此需要在数值上加1并转型位char类型
board[i][j] = (char) ('1' + num);
// 将row、col以及block状态数组对应得状态设置为true
row[i][num] = true;
col[j][num] = true;
block[blockIndex][num] = true;
// 递归解数独
if (dfs(board, row, col, block, i, j)) return true;
else {
// 若填入数字失败则回溯
row[i][num] = false;
col[j][num] = false;
block[blockIndex][num] = false;
board[i][j] = '.';
}
}
}
return false;
}
}
求解测试类:
public class SudokuSolutionTest {
public static void main(String[] args) {
char[][] board = new char[][]{
{'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'}
};
printBoard(board);
Solution solution = new Solution();
solution.solveSudoku(board);
printBoard(board);
}
/**
* 打印数独函数
* @param board
*/
private static void printBoard(char[][] board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
}
}