题目
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.
A sudoku puzzle...
...and its solution numbers marked in red.
解法
解法1
解法1的思路就是简单的回溯,找到第一个待填方格,然后从‘1’到‘9’试,先检查是否在同一行,同一列或者3*3的小方格内有冲突,若没有冲突,则标记该方格,递归去找下一个待填方格,如果所有的方格都已添好,则返回解。
代码如下:
class Solution {
public:
void solveSudoku(vector<vector<char> > &board) {
internalSolveSudoku(board);
}
private:
bool internalSolveSudoku(vector<vector<char> > &board){
for(int row = 0; row < 9; ++row){
for(int col = 0; col < 9; ++col){
if('.' == board[row][col]){//该格未填值
for(int k = 1; k <=9; ++k){//尝试填入1到9
board[row][col] = '0' + k;
if(check(board, row, col)){//检查在第row行第col列填入k是否合法
if(internalSolveSudoku(board)){//若合法,尝试填下一空
return true;//找到解
}
}//if(check(board, row, col))
board[row][col] = '.';//若不合法,清除k
}//for(int k = 1; k <=9; ++k)
return false;
}//if('.' == board[row][col])
}//for(int col = begin_col; col < 9; ++col)
}//for(int row = 0; row < 9; ++row)
return true;//找到解
}
bool check(vector<vector<char> > &board, int row, int col){
//检查第row行是否有重复
for(int i = 0; i < 9; ++i){
if(i != col && board[row][i] == board[row][col]){
return false;//发现重复
}
}
//检查第col列是否有重复
for(int i = 0; i < 9; ++i){
if(i != row && board[i][col] == board[row][col]){
return false;//发现重复
}
}
//检查第row行第col列所在的3*3方格是否有重复
int begin_row_block = row / 3 * 3;//3*3方格的开始行号
int begin_col_block = col / 3 * 3;//3*3方格的开始列号
int end_row_block = begin_row_block + 3;//3*3方格的结束行号
int end_col_block = begin_col_block + 3;//3*3方格的结束列号
for(int i = begin_row_block; i < end_row_block; ++i){
for(int j = begin_col_block; j < end_col_block; ++j){
if((i != row || j != col) && board[i][j] == board[row][col]){
return false;//发现重复
}
}
}
return true;//无重复
}
};
解法2
观察解法1,发现每次递归都从board[0][0]开始是没有必要的,只需从当前方格的右侧方格开始递归即可。若当前方格已经是该行的最后一个方格,则需从下一行的第一个方格开始递归。代码如下:
class Solution {
public:
void solveSudoku(vector<vector<char> > &board) {
internalSolveSudoku(board, 0, 0);
}
private:
bool internalSolveSudoku(vector<vector<char> > &board, int begin_row, int begin_col){
int row = begin_row;
int col = begin_col;
while(row < 9){
if('.' == board[row][col]){
for(int k = 1; k <=9; ++k){
board[row][col] = '0' + k;
if(check(board, row, col)){
if(col < 8){
if(internalSolveSudoku(board, row, col+1)){
return true;
}
}//if(col < 8)
else{
if(internalSolveSudoku(board, row + 1, 0)){
return true;
}
}//else
}//if(check(board, row, col))
board[row][col] = '.';
}//for(int k = 1; k <=9; ++k)
return false;
}//if('.' == board[row][col])
++col;
if(col > 8){
col = 0;
++row;
}
}//while(row < 9)
return true;
}
//检查函数同解法1
bool check(vector<vector<char> > &board, int row, int col){
//check row
for(int i = 0; i < 9; ++i){
if(i != col && board[row][i] == board[row][col]){
return false;
}
}
//check column
for(int i = 0; i < 9; ++i){
if(i != row && board[i][col] == board[row][col]){
return false;
}
}
//check the 3*3 block
int begin_row_block = row / 3 * 3;
int begin_col_block = col / 3 * 3;
int end_row_block = begin_row_block + 3;
int end_col_block = begin_col_block + 3;
for(int i = begin_row_block; i < end_row_block; ++i){
for(int j = begin_col_block; j < end_col_block; ++j){
if((i != row || j != col )&& board[i][j] == board[row][col]){
return false;
}
}
}
return true;
}
};
解法3
解法3通过记录每行,每列,每个3*3方格的剩余可填数字,以缩短检查重复的时间。本来想用bitset表示剩余可填数字,不过leetcode貌似不支持,只能使用一个大小为9的vector<bool> b表示了。例如,b[0]为true,表示数字1可填,b[0]为false,表示数字1已经出现过了,不能填。3*3方格共有9个,编号如下所示:
0 1 2
3 4 5
6 7 8
很明显3*3方格的编号和行号列号之间存在对应关系如下;
方格编号 = 行号 / 3 * 3 + 列号 / 3
这里的除法都是整数除法,例如 1 / 3 = 0
在递归的过程中,每次填入一个数字,都需要更新相应的行、列和3*3方格。而重置方格为未填状态时,也需更新相应的行、列和3*3方格。
代码如下:
class Solution {
public:
void solveSudoku(vector<vector<char> > &board) {
vector<bool> b;//b[i]为true,表示数字‘i+1’还未使用。例如若b[0]为true,则表示数字‘1’未使用。
b.assign(9, true);
column_left_.assign(9,b);
line_left_.assign(9,b);
sub_box_left_.assign(9,b);
int sub_box_order = 0;
int n = 0;
for(int i = 0; i < 9; ++i){
for(int j = 0; j < 9; ++j){
if(board[i][j] != '.'){
n = board[i][j] - '1';
line_left_[i][n] = false;
column_left_[j][n] = false;
sub_box_order = i / 3 * 3 + j / 3;//由行号列号得到3*3表方格的编号
sub_box_left_[sub_box_order][n] = false;
}//if(board[i][j] != '.')
}//for(int j = 0; j < 9; ++j)
}//for(int i = 0; i < 9; ++i)
internalSolveSudoku(board, 0, 0);
}
private:
bool internalSolveSudoku(vector<vector<char> > &board, int begin_row, int begin_col){
int row = begin_row;
int col = begin_col;
while(row < 9){
if('.' == board[row][col]){
int sub_box_order = row / 3 * 3 + col / 3;
for(int k = 0; k < 9; ++k){
if(line_left_[row][k] && column_left_[col][k] && sub_box_left_[sub_box_order][k]){//数字‘k+1’尚未使用,即填入数字‘k+1’不会引起冲突
board[row][col] = '1' + k;
line_left_[row][k] = false;
column_left_[col][k] = false;
sub_box_left_[sub_box_order][k] = false;
if(col < 8){
if(internalSolveSudoku(board, row, col+1)){
return true;
}
}//if(col < 8)
else{
if(internalSolveSudoku(board, row + 1, 0)){
return true;
}
}//else
board[row][col] = '.';
line_left_[row][k] = true;
column_left_[col][k] = true;
sub_box_left_[sub_box_order][k] = true;
}//if(line_left_[row][k] && column_left_[col][k] && sub_box_left_[sub_box_order][k])
}//for(int k = 1; k <=9; ++k)
return false;
}//if('.' == board[row][col])
++col;
if(col > 8){
col = 0;
++row;
}
}//while(row < 9)
return true;
}
vector<vector<bool>> line_left_;//每行剩下可以填的数字,一共9行,每行有9个bool值
vector<vector<bool>> column_left_;//每列剩下可以填的数字,一共9列,每列有9个bool值
vector<vector<bool>> sub_box_left_;//每个3*3的小方格可以填的数字,一共9个方格,每个方格有9个bool值
};
三种解法的时间对比如下表所示:
算法 | 时间 |
1 | 236ms |
2 | 192ms |
3 | 124ms |
另一篇博客提到了更快的一种解法