转载请注明出处:http://write.blog.csdn.net/postedit/52739077
原题: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.
备注:满足条件的填充要满足,每行、每列、每个方格(9个小方格)数字包含1-9
即数字不重复。
解答:
C++版本
class Solution {
public:
void solveSudoku(vector<vector<char>>& board) {
solve(board);
}
private:
bool solve(vector<vector<char>>& board) {
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
if (board[r][c] == '.') {
for (char d = '1'; d <= '9'; d++) {
if (isValid(board, r, c, d)) {
board[r][c] = d;
if (solve(board)) return true;
}
}
board[r][c] = '.';
return false;
}
}
}
return true;
}
bool isValid(vector<vector<char>>& board, int r, int c, char d) {
for (int row = 0; row < 9; row++)
if (board[row][c] == d) return false;
for (int col = 0; col < 9; col++)
if (board[r][col] == d) return false;
for (int row = (r / 3) * 3; row < (r / 3 + 1) * 3; row++) //判断小方格是否重复
for (int col = (c / 3) * 3; col < (c / 3 + 1) * 3; col++)
if (board[row][col] == d) return false;
return true;
}
};
思路:
和迷宫问题类似,这道题也是采用回溯的方法,这也是最容易想到的解决方法。
数据检验函数isValid用来检验数据是否重复,比较好理解,关键是回溯方式的理解。
当然,这里的回溯法必然要用到递归来实现,对于一个未完成的数独,我们只有对发现的第一个空格(程序中用“.”表示)用全部有效数字(通过isValid检验的数)验证一遍,才知道是否能成功,这里的双层for循环就是为了发现第一个空格。
第一个空格每次填充一个有效数字后,就可以调用solve函数来验证填充完这个数后,是否能成功,这也是递归的灵魂,是一种归纳的思想,犹如,我们需要求解f(n)的值,可以使用f(n-1),而且认为f(n-1)是已知的。
递归返回的条件,如果找到任意一个有效解就返回true;或者整个空格所有有效数都用完了,依然没有找不到成功解,返回false;或者没有找到空格,说明大功告成,这就是我们想要的数独,当然要返回true。
需要注意的是,board[r][c] = '.'语句的作用。由于使用的board是引用方式,那么任意一处的修改都会影响其他地方的使用。所以所有值都不满足时,所在方格要还原为“.”,这样就不会影响上层递归的判断。
在实际运行过程中是这样的:
第1个空格填充一个有效数字,比如是1,进入solve... //第 1 层递归
第2个空格(相对最原始的表)填充一个有效数字,比如是4,进入solve... //第 2 次递归
第3空格填充一个有效数字,比如是3,进入solve... //第 3 层递归
...
倒数第2个空格填充一个有效数字,比如是7,进入solve... //倒数第 2 层递归
最后一个空格填充一个有效数字,发现找不到这样的有效数字 //最后一层
最后一个空格还原为‘.’返回false,退回到倒数上层递归...
上层递归是填充倒数第2个空格那层,说明填7不对,改为另一个有效数字 //倒数第 2 层递归
比如4,继续solve...
最后一个空格填充一个有效数字,发现找也不到这样的有效数字 //又回到最后一层
最后一个空格还原为‘.’返回false,退回到倒数上层递归...
上层递归是填充倒数第2个空格那层,说明填4也不对,改为另一个有效数字 //倒数第 2 层递归
比如2,继续solve...
最后一个空格填充一个有效数字,发现找不到这样的有效数字 //第三次回到最后一层
继续回到上层循环,但是发现有效数字用完了,那就再回到上层 //回到倒数第二层递归,但是数字已用尽
上层递归是填充倒数第3个空格那层,说明第三层也要改数字了 //倒数第三层递归
.......
.......
所有结果都无效,最终函数要回到第一层递归,假设第一层(就是第一个空格处)可用的数字有3个,1、5、8
那说明1已经试验过,统统无效,那么就会去尝试5,重复上面的整个过程,可能有效,也可能无效,如果无效
那说明8一定有效,因为题目保证有唯一一个正确答案。
。。。。。。。
上面就是回溯和递归的全过程,需要说明的一点事,可能在没有填充完最后的空格就发现找不到有效数字了,这样递归就提前返回到上层,最理想的状态是所有递归都能找到合理数字,都不要还原数字(把答案中所有的1抠掉就是一个例子)。说了那么多,就是为了说明这种深度优先遍历的执行步骤,计算机其实很傻,但是很听话,不会出错,层层递归,不满足就返回上层,修改值,重新进入,直到找到答案为止。