Problem: 37. 解数独
思路:
利用row[i][val]表示第i行有没有val出现。
利用col[j][val]表示第j列有没有val出现。
利用block[i / 3][j / 3][val]表示第(i/3,j/3)个九宫格有没有val出现。
利用empCells存储尚未被填充的点。
首先取出empCells中的第一个点,digital从0~9填充,如果digital不在行,列,其所在九宫格中存在,则遍历下一个未被填充的点。直到填充结束。
注意点:
1、在digital遍历的时候,需要加上 !valid 条件。也就是(int digital = 0; digital < 9 && !valid; digital ++)。为什么?
因为如果不加这个条件,假设找到数独的解时,整个dfs还未完成。就要进行回溯,回溯的时候会将先前已经遍历过的digital设置为false。这样的话,当回溯到某个点时,该点的digital从k~9遍历(k是未被遍历过的第一个数字),而恰好在之前回溯过程中,k被设置为false,k在该点的行,列,所在九宫格中均为出现,那么k就会覆盖之前的解中的board[i][j],使得解出错。
举个例子,如下图。dfs在最后的红色1处找到解。此时进行回溯。它一定会回溯到蓝色的1处。这时,由于1下方的7和有方的7都已经被释放了。那么这个时候蓝色的1处就可以填充7,然后接着遍历。就会覆盖正确解的数值。因为board[i][j]在回溯的时候,我们并没有进行对board[i][j]恢复现场。我们仅仅采用了覆盖之前值的方式来回溯。

2、不能使用队列来存储未被确定的点。因为频繁的出队、入队,相当耗时。
是的,自己看图吧。说多了都是泪。


Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution { private boolean[][] row = new boolean[9][9]; private boolean[][] col = new boolean[9][9]; private boolean[][][] block = new boolean[3][3][9]; List<int[]> empCells = new ArrayList<int[]>(); boolean valid;
public void solveSudoku(char[][] board) { for (int i = 0; i < 9; i ++) { for (int j = 0; j < 9; j ++) { if (board[i][j] == '.') { empCells.add(new int[]{i, j}); } else { int digital = board[i][j] - '1'; row[i][digital] = col[j][digital] = block[i / 3][j / 3][digital] = true; } } } dfs(board, 0); }
private void dfs(char[][] board,int index) { if (index == empCells.size()) { valid = true; return ; } int[] e = empCells.get(index); int x = e[0], y = e[1]; for (int i = 0; i < 9 && !valid; i ++) { if (!row[x][i] && !col[y][i] && !block[x / 3][y / 3][i]) { row[x][i] = col[y][i] = block[x / 3][y / 3][i] = true; board[x][y] = (char) ('1' + i); dfs(board, index + 1); row[x][i] = col[y][i] = block[x / 3][y / 3][i] = false; } } } }
|