题目描述
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字 1-9 在每一行只能出现一次。
- 数字 1-9 在每一列只能出现一次。
- 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
题解:回溯
遍历顺序都是从空格开始依次向后遍历。两种回溯模板:
第一种模板:
先将数独中的信息保存到三个bool数组中,一遍后续判断是否行列以及3*3宫格内是否出现过该数字,三个bool数组的含义。row[x][k]表示在第x行是否出现了数字k。cell[i][j][k]表示在第i行第j列的小单元格子里是否出现了数字k。
- 如果遍历到了某一行最后一列,那么就要返回到下一行执行,也就是将行下标变为自加1,列下标变为0。如果遍历到了x==9,那么返回true。
- 如果该位置是数字,那么继续向下遍历,行下标不变,列下标加一
- 否则就需要判断当前位置是否应该填写该数字,就需要判断行列以及3*3宫格内是否写过该数字,如果没写过就在遍历到的位置写入该数字,之后撤销选择。
class Solution {
bool row[9][9];
bool col[9][9];
bool cell[3][3][9];
public:
void solveSudoku(vector<vector<char>> &board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int t = board[i][j] - '1';
row[i][t] = col[j][t] = cell[i / 3][j / 3][t] = true;
}
}
}
dfs(board, 0, 0);
}
bool dfs(vector<vector<char>> &board, int x, int y) {
if (y == 9)
{
y=0;x++;
}
if (x == 9) return true;
if (board[x][y] != '.') return dfs(board, x, y + 1);
for (int i = 0; i < 9; i++) {
if (!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]) {
board[x][y] = (char)(i + '1');
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
//得到一组结果就返回
if (dfs(board, x, y + 1)) {
break;
} else {//撤销选择
board[x][y] = '.';
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
}
}
}
return board[x][y] != '.';
}
};
看出这两个版本的区别了吗?
一个在得到解答的时候break,此时函数还并未返回到调用函数,另一个是直接返回true,直接返回到调用函数位置,因此二者最后一行的返回语句是不一样的。return语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。具体执行过程我想了一下应该是这样的,对于上一个模板,break之后需要判断当前位置是否返回的是true,如果是就继续向递归树上走直到返回到最终原始调用的函数。对于下一个模板,直接返回到调用该函数的地方,不需要做判断。return false表示的含义是如果这时候都没有返回true,那么条件不满足返回false。因此两个模板后者比前者执行时间要快一些。
class Solution {
bool row[9][9];
bool col[9][9];
bool cell[3][3][9];
public:
void solveSudoku(vector<vector<char>> &board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int t = board[i][j] - '1';
row[i][t] = col[j][t] = cell[i / 3][j / 3][t] = true;
}
}
}
dfs(board, 0, 0);
}
bool dfs(vector<vector<char>> &board, int x, int y) {
if (y == 9)
{
y=0;x++;
}
if (x == 9) return true;
if (board[x][y] != '.') return dfs(board, x, y + 1);
for (int i = 0; i < 9; i++) {
if (!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]) {
board[x][y] = (char)(i + '1');
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
if (dfs(board, x, y + 1)) {
return true;
} else {
board[x][y] = '.';
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
}
}
}
return false;
}
};
第二种模板:
这种模板是在遍历到该位置时再判断是否可以填入该数字。注意返回false的位置与含义。
class Solution {
public:
void solveSudoku(vector<vector<char> > &board) {
backtracing(board);
}
bool backtracing(vector<vector<char>>& board)
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(board[i][j]!='.') continue;
for(char k='1';k<='9';k++)
{
if(isvalid(i,j,k,board))
{
board[i][j]=k;//该位置填写k
if(backtracing(board)) return true;
board[i][j]='.';//撤销选择
}
}
return false;//九个数字都没办法填写返回false
}
}
return true;//所有情况没返回false,则返回true
}
bool isvalid(int a,int b, char k,vector<vector<char>>& board)
{
int n=board.size();
for(int i=0;i<n;i++)
{
if(board[a][i]==k) return false;
}
for(int j=0;j<n;j++)
{
if(board[j][b]==k) return false;
}
int col = (a/3)*3;
int row = (b/3)*3;
for(int i=col;i<col+3;i++)
{
for(int j=row;j<row+3;j++)
{
if(board[i][j]==k) return false;
}
}
return true;
}
};