编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
输入:
board = [
["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"]
]
由于每个数字在同一行、同一列、同一个九宫格中只会出现一次,因此我们可以使用三个状态数组来表达这三种约束。
考虑到状态仅有两种可能,使用位运算来实现以减少空间消耗。具体地,数 b的二进制表示的第 i位(从低到高,最低位为第 0 位)为 1,当且仅当数字 i+1 已经出现过。例如当 b 的二进制表示为 (011000100)2时,就表示数字 3,7,8 已经出现过。
状态压缩
+回溯
使用数组line[9],column[9],block[3][3]来压缩存储每一行、每一列、每一个 3x3 宫格中 1-9 是否出现 。具体来说,line[0]表示第一行的1-9是否出现,如line[0]=111111111 表示第一行每个数字都出现了。
2. 这样每一个格子就可以计算出所有不能填的数字,然后得到所有能填的数字 getPossibleStatus()
3. 填入数字和回溯时,只需要更新存储信息
4. 每个格子在使用时,会根据存储信息重新计算能填的数字
```java class Solution { \(也可以声明为局部变量以节省一丢丢的空间,但是方法参数会多四个) private int[] line = new int[9]; private int[] column = new int[9]; private int[][] block = new int[3][3]; private boolean valid = false; private Listspaces = new ArrayList (); public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] == '.') { spaces.add(new int[]{i, j}); } else { int digit = board[i][j] - '0' - 1; flip(i, j, digit); } } } dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) { valid = true; return; } int[] space = spaces.get(pos); int i = space[0], j = space[1]; \\(之所以要 & 0x1ff是因为取非后,除了最后9位前面的一堆都是1) int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff; for (int k=0;k<9 && !valid;k++) { if((mask&1<<k)==0) continue; flip(i, j, k); board[i][j] = (char) (k + '0' + 1); dfs(board, pos + 1); flip(i, j, k); }
}
public void flip(int i, int j, int digit) {
line[i] ^= (1 << digit); column[j] ^= (1 << digit); block[i / 3][j / 3] ^= (1 << digit);
} }
```