典型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)完毕之后,应该返回错误,这个是不应该被忽略的。
http://blog.csdn.net/zxzxy1988/article/details/8586289.
package Level4; /** * 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. * */ public class S37 { public static void main(String[] args) { char[][] board = { {'.','.','9','7','4','8','.','.','.'}, {'7','.','.','.','.','.','.','.','.'}, {'.','2','.','1','.','9','.','.','.'}, {'.','.','7','.','.','.','2','4','.'}, {'.','6','4','.','1','.','5','9','.'}, {'.','9','8','.','.','.','3','.','.'}, {'.','.','.','8','.','3','.','2','.'}, {'.','.','.','.','.','.','.','.','6'}, {'.','.','.','2','7','5','9','.','.'}, }; solveSudoku(board); for(int i=0; i<9; i++){ for(int j=0; j<9; j++){ System.out.print(board[i][j]); } System.out.println(); } } public static void solveSudoku(char[][] board) { dfs(board, 0, 0); } public static boolean dfs(char[][] board, int x, int y) { if(x>8 || y>8){ // 全部深搜完毕 return true; } if (board[x][y] == '.') { // 如果当前是空格 for (int k = 1; k <= 9; k++) { if (isValid(board, x, y, (char) ('0' + k))) { // 说明在当前空格找到一个满足条件的数字 board[x][y] = (char) ('0' + k); int nextX = x; int nextY = y+1; if(nextY == 9){ nextY = 0; nextX++; } if(dfs(board, nextX, nextY)){ // 对下一个空格搜索数字,如果下一个位置找到满足条件的数字,就此返回。否则改变当前空格的数字继续测试 return true; } board[x][y] = '.'; } } return false; // 对于当前空格,如果所有的数字都不满足,则无解! } else{ // 如果当前已经有数字,就跳过继续深搜 int nextX = x; int nextY = y+1; if(nextY == 9){ nextY = 0; nextX++; } return dfs(board, nextX, nextY); } } public static boolean isValid(char[][] board, int x, int y, char k) { for(int i=0; i<9; i++){ // 同列检查 if(board[i][y] == k){ return false; } if(board[x][i] == k){ // 同行检查 return false; } } for(int i=0; i<3; i++){ // 九宫格检查 for(int j=0; j<3; j++){ if(board[3*(x/3)+i][3*(y/3)+j] == k){ return false; } } } return true; } }
循环处理子问题:
对于每个格子,带入不同的9个数,然后判合法,如果成立就递归继续,结束后把数字设回空。
public class Solution { public void solveSudoku(char[][] board) { rec(board, 0, 0); } public boolean rec(char[][] board, int x, int y) { if(x > 8) { return true; } if(y > 8) { return rec(board, x+1, 0); } if(board[x][y] == '.') { for(int i=1; i<=9; i++) { board[x][y] = (char)('0' + i); // after setting if(isValid(board, x, y)) { // make sure board is valid if(rec(board, x, y+1)) { // if we find a solution, return true return true; } } board[x][y] = '.'; } return false; // we have tried all possibilities } else { return rec(board, x, y+1); } } public boolean isValid(char[][] board, int x, int y) { for(int i=0; i<9; i++) { if(i != x && board[i][y] == board[x][y]) { return false; } } for(int j=0; j<9; j++) { if(j != y && board[x][j] == board[x][y]) { return false; } } for(int i=x/3*3; i<x/3*3+3; i++) { // find which block first, then find that row for(int j=y/3*3; j<y/3*3+3; j++) { if((i != x || j != y) && board[i][j] == board[x][y]) { return false; } } } return true; } }
http://blog.csdn.net/linhuanmars/article/details/20748761