题目:数独
描述:请编写一个程序,给数独中的剩余的空格填写上数字
空格用字符'.'表示
解法一:
思路分析:首先我们通过该题目了解一下什么是数独,数独是十八世纪在瑞士产生的一种小游戏,是通过不断的演算推导出来的逻辑游戏,在上述中,属于9行9列的格子,且被分为9个3*3的方块,该方块内的数字取值范围为1-9,玩家通过在剩余的空白格子中填入数字,数字需要满足的条件为数字1-9在每一行每一列只能出现一次,且数字1-9在每一个粗实线分隔的3*3宫内只能出现一次。
我们需要做的就是,找出当前可能存在的各种解法,不断地填入可能的数字,如果通过演算得出的解法错误就将该结果撤回,否则的话,就已知尝试不同的解法,通过上述描述,我们可以想到的首先是递归的方法,通过尝试不同的可能的解,向上返回所有的尝试结果,可能的结果只有两种,一种为成功,一种为失败,具体采用的递归方法为:
1、首先推测出每一个空白的格子中可能存在的数字
2、找到可供选择的最小的那个格子
3、如果有可选的数字,填入可选的数字,生成一个数独,否则的话就返回失败的信息
4、不断的通过调用递归函数来处理新的数独
5、不断的填入新的数字进行判断,当成功则返回成功的信息,当失败则重新进行递归判断。
6、每成功一次就向上一层返回成功的信息
因为空白格子有一定的数量,所以我们使用1-9来进行记录,采用unsigned int类型进行记录从右往左数的第K为代表的数字K,使用1定位相应的位置,使用或和与运算进行相关的操作,对于i行j列的小方格,使用i / 3 * 3 + j / 3即可以得出相应3 * 3方块的下标。
实例分析:在一个实例分析中我们采用表格进行判断,简化操作
| 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 |
| 5 | 3 | 1,2,4 |
| 6 |
2,7 | 2,4,7 |
| 1,2 | 9 | 8 |
| 5 | 3 | 4 |
| 6 | 2 | 7 |
| 1 | 9 | 8 |
| 5 | 3 | 4 |
| 6 | 7 | 2 |
| 1 | 9 | 8 |
具体C++代码为:
class Solution {
public:
void solveSudoku(vector<vector<char>>& board) {
unsigned int pos_r[9];//每一行剩下的可选数字
unsigned int pos_l[9];//每一列剩下的可选数字
unsigned int pos_b[9];//每一方块剩下的可选数字
//九个数字均可选
for (int i = 0; i < 9; i++){
pos_r[i] = 0b111111111;
pos_l[i] = 0b111111111;
pos_b[i] = 0b111111111;
}
for (int i = 0; i < 9; i++){
for (int j = 0; j < 9; j++){
if(board[i][j] != '.' ){//出现了新的数字,空白格用点号表示
unsigned int bit = 1 << (board[i][j] - '1');
pos_r[i] &= ~bit;//把相应的位置为0
pos_l[j] &= ~bit;
pos_b[i/3*3+j/3] &= ~bit;
}
}
}
slove(board, pos_r, pos_l, pos_b);//调用函数
}
bool slove(vector<vector<char>>& board, unsigned int *pos_r, unsigned int *pos_l, unsigned int *pos_b){
int min_choice = 10;
int a = 0, b = 0;
unsigned int pos_bit = 0;
for (int i = 0; i < 9; i++){
for (int j = 0; j < 9; j++){
if(board[i][j] == '.' ){
//该位置没有填
unsigned int possible = pos_r[i] & pos_l[j] & pos_b[i/3*3+j/3];//计算出同时满足三个条件的数字
if (possible == 0) return false;//无解
int choice = __builtin_popcount(possible);//计算可能的数字有多少(1的位数)
if (choice < min_choice){//找到可填入数字最少的
min_choice = choice;
a = i;
b = j;
pos_bit = possible;
}
}
}
}
if (min_choice == 10)
return true; //全部填完了,返回
//pos_bit 记录可能的数字
while(pos_bit){
unsigned int test_bit = 1 << __builtin_ctz(pos_bit);//从最右边的位开始(选最小的数字)
board[a][b] = __builtin_ctz(pos_bit) + '1';//填入数字
pos_r[a] &= ~test_bit;
pos_l[b] &= ~test_bit;
pos_b[a/3*3+b/3] &= ~test_bit;
if (slove(board, pos_r, pos_l, pos_b)){
return true;//成功 返回
}
//失败,撤回后继续试
board[a][b] = '.';
pos_r[a] |= test_bit;
pos_l[b] |= test_bit;
pos_b[a/3*3+b/3] |= test_bit;
//清除试过的位
pos_bit &= ~test_bit;
}
return false;//找不到解
}
};
因为要遍历每一行每一列的元素来进行判断,所以其时间复杂度为O(N^2),N为i行或j列,用三个数组表示可选择的数字,空间复杂度为O(N * i * j)。
解法二:
思路分析:上述代码较为复杂,可以采用暴力法代码,主要遵循三个基本原则每一行只能出现1~9这九个数字,并且不能重复,每一列只能出现1~9这九个数字,并且不能重复。每一个九宫格里面只能出现1~9这九个数字,不能重复,可直接通过暴力解决。
具体Java代码为:
public class Solution {
public void solveSudoku(char[][] board) {
if(board == null || board.length == 0)//特殊情况
return;
solve(board);
}
public boolean solve(char[][] board){
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[0].length; j++){
if(board[i][j] == '.'){
for(char c = '1'; c <= '9'; c++){//尝试1-9的情况
if(isValid(board, i, j, c)){
board[i][j] = c; //将c的值放进去
if(solve(board))
return true; //如果解决了返回true
else
board[i][j] = '.'; //其他的返回.
}
}
return false;
}
}
}
return true;
}
private boolean isValid(char[][] board, int row, int col, char c){
for(int i = 0; i < 9; i++) {
if(board[i][col] != '.' && board[i][col] == c)//检查行
return false;
if(board[row][i] != '.' && board[row][i] == c)//检查列
return false;
if(board[3 * (row / 3) + i / 3][ 3 * (col / 3) + i % 3] != '.' && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c)//检查子宫格
return false;
}
return true;
}
}
其时间复杂度为O(N^3),因为包括行列的循环和数字1-9的循环,所以最大时间复杂度为O(N^3),空间复杂度也为O(N^3)。

京公网安备 11010502036488号