Sudoku Solver
Write a program to solve a Sudoku puzzle by filling the empty cells.
Empty cells are indicated by the character '.'.
You may assume that there will be only one unique solution.
SOLUTION:
ref: http://blog.csdn.net/fightforyourdream/article/details/16916985
采用递归+回溯模板来解决此问题:
1. 判定DFS的退出条件。
(1) y越界,应该往下一行继续解。
(2)x越界,代表数独解完,应该返回。
2. DFS的主体部分:
把9个可能的值遍历一次,如果是OK的(使用独立 的Valid函数来判定行,列,BLOCK是否符合),继续DFS,否则回溯。
3. 最后的返回值:
如果所有的可能性遍历都没有找到TRUE,最后应该返回FALSE,也就是当前情况无解。这个时候DFS会自动回溯到上一层再查找别的可能解。
1 public static void main(String[] args) { 2 char[][] board = { 3 {'.','.','9','7','4','8','.','.','.'}, 4 {'7','.','.','.','.','.','.','.','.'}, 5 {'.','2','.','1','.','9','.','.','.'}, 6 {'.','.','7','.','.','.','2','4','.'}, 7 {'.','6','4','.','1','.','5','9','.'}, 8 {'.','9','8','.','.','.','3','.','.'}, 9 {'.','.','.','8','.','3','.','2','.'}, 10 {'.','.','.','.','.','.','.','.','6'}, 11 {'.','.','.','2','7','5','9','.','.'}, 12 }; 13 solveSudoku(board); 14 for(int i=0; i<9; i++){ 15 for(int j=0; j<9; j++){ 16 System.out.print(board[i][j]); 17 } 18 System.out.println(); 19 } 20 } 21 22 public void solveSudoku1(char[][] board) { 23 dfs1(board, 0, 0); 24 } 25 26 public boolean dfs1(char[][] board, int x, int y) { 27 // go to next row. 28 if (y == 9) { 29 y = 0; 30 x++; 31 } 32 33 // done 34 if (x >= 9) { 35 return true; 36 } 37 38 // Skip the solved point. 39 if (board[x][y] != '.') { 40 return dfs1(board, x, y + 1); 41 } 42 43 // Go throught all the possibilities. 44 for (int k = 0; k < 9; k++) { 45 board[x][y] = (char)('1' + k); 46 // SHOULD RETURN HERE IF INVALID. 47 if (isValid1(board, x, y) && dfs1(board, x, y + 1)) { 48 return true; 49 } 50 board[x][y] = '.'; 51 } 52 53 // because all the possibility is impossiable. 54 return false; 55 } 56 57 public boolean isValid1(char[][] board, int x, int y) { 58 // Judge the column. 59 for (int i = 0; i < 9; i++) { 60 if (i != x && board[i][y] == board[x][y]) { 61 return false; 62 } 63 } 64 65 // Judge the row. 66 for (int i = 0; i < 9; i++) { 67 if (i != y && board[x][i] == board[x][y]) { 68 return false; 69 } 70 } 71 72 // Judge the block. 73 int i = x / 3 * 3; 74 int j = y / 3 * 3; 75 for (int k = 0; k < 9; k++) { 76 int xIndex = i + k / 3; 77 int yIndex = j + k % 3; 78 if (xIndex == x && yIndex == y) { 79 continue; 80 } 81 82 if (board[xIndex][yIndex] == board[x][y]) { 83 return false; 84 } 85 } 86 87 return true; 88 }
摘抄部分解释:
典型DFS/递归/回溯/深搜题。对于DFS,说白了
1) 什么时候返回?在本题中,
1.当x>8或y>8 表示已经遍历完所有的格子,因此成功完成,返回true。
2.当下一个搜索(子搜索)返回true,说明已经找到,返回true。
3.如果测试过本轮的所有可能解,但无一是对的,说明无解,返回false。
4.如果当前空格不是空格,则改变x,y坐标后,继续下一个空格的尝试
2)DFS就是针对本轮的所有可能解进行逐一尝试,找到本轮的一个可能解后,这时要调用递归,基于本轮的解对下一轮(子问题)进行求解。如果下一轮(子问题)求解成功,则说明大功告成,及时返回true,停止之后的尝试。
否则如果下一轮(子问题)求解失败,则说明本轮的解不适合子问题,因此,必须换一个本轮的解,然后基于本轮的新解,继续尝试子问题。如果已经本轮所有的解都尝试过了,也都失败了,说明本问题无解,返回false。
当然在每次尝试子问题前和如果失败返回后,都要恢复原来的环境(撤销动作)。
所以,要想使DFS成功返回,条件就是找到满足本轮的解和这个解也要满足下一轮(子问题)。
另外:
1 每个backtracking的题目,最好都有独立判断isValid的程序,这样架构清楚。同时,valid判断函数在这里可以稍微研究一下。只要当前要判断的位置上的数值和本行没有重复,本列没有重复,九宫格没有重复就可以。一旦重复立即返回,减少判断次数。
2 backtracking的递归函数,怎么能没有返回值呢?因为要判断递归的方案正确与否,所以这里的递归一定是有返回值的(除非是combination那种没有正确错误概念的backtracking)
3 可以考虑“先放置,再判断”的方案。比如这里,首先判断当前位置是否为空,如果为空,那么放置一个元素,检查它是否正确。如果正确,就继续进行下面的递归(也就是第29行 isValid&&solveSudoku的作用)。当函数返回错误之后,将刚刚的数值变为空,再进行下一次尝试即可。
4 所有的方案(k从1到9)完毕之后,应该返回错误,这个是不应该被忽略的。
2015.1.13 redo:
1 public class Solution { 2 public void solveSudoku(char[][] board) { 3 // 3:01
4 if (board == null || board.length == 0 || board[0].length == 0) { 5 return; 6 } 7
8 dfs(board, 0, 0); 9 } 10
11 public boolean dfs(char[][] board, int x, int y) { 12 // 3:01 13 // next row.
14 if (y >= 9) { 15 return dfs(board, x + 1, 0); 16 } 17
18 if (x >= 9) { 19 return true; 20 } 21
22 // skip the number.
23 if (board[x][y] != '.') { 24 return dfs(board, x, y + 1); 25 } 26
27 // solve the current node. 28 // BUG2: c start from 1 not 0.
29 for (char c = '1'; c <= '9'; c++) { 30 board[x][y] = c; 31 if (isValid(board, x, y, c) && dfs(board, x, y + 1)) { 32 return true; 33 } 34 board[x][y] = '.'; 35 } 36
37 return false; 38 } 39
40 public boolean isValid(char[][] board, int x, int y, char c) { 41 // the current row.
42 for (int i = 0; i < 9; i++) { 43 if (y != i && c == board[x][i]) { 44 return false; 45 } 46 } 47
48 // the current col.
49 for (int i = 0; i < 9; i++) { 50 // BUG1: should use board[i][y]
51 if (x != i && c == board[i][y]) { 52 return false; 53 } 54 } 55
56 // the current block.
57 int startX = x / 3 * 3; 58 int startY = y / 3 * 3; 59 for (int k = 0; k < 9; k++) { 60 int indexX = startX + k / 3; 61 int indexY = startY + k % 3; 62 if (indexX == x && indexY == y) { 63 continue; 64 } 65
66 if (board[indexX][indexY] == c) { 67 return false; 68 } 69 } 70
71 return true; 72 } 73 }
GITHUB代码:
https://github.com/yuzhangcmu/LeetCode_algorithm/blob/master/hash/SolveSudoku.java