leetcode 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.


A sudoku puzzle...


...and its solution numbers marked in red.

解法

解法1

解法1的思路就是简单的回溯,找到第一个待填方格,然后从‘1’到‘9’试,先检查是否在同一行,同一列或者3*3的小方格内有冲突,若没有冲突,则标记该方格,递归去找下一个待填方格,如果所有的方格都已添好,则返回解。
代码如下:
class Solution {
public:
    void solveSudoku(vector > &board) {
		internalSolveSudoku(board);
	}

private:
	bool internalSolveSudoku(vector > &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 > &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 > &board) {
		internalSolveSudoku(board, 0, 0);
	}

private:
	bool internalSolveSudoku(vector > &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 > &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 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 > &board) {
		vector 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 > &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> line_left_;//每行剩下可以填的数字,一共9行,每行有9个bool值
	vector> column_left_;//每列剩下可以填的数字,一共9列,每列有9个bool值
	vector> sub_box_left_;//每个3*3的小方格可以填的数字,一共9个方格,每个方格有9个bool值
};

三种解法的时间对比如下表所示:

算法

时间

1

236ms

2

192ms

3

124ms

leetcode上的执行情况如下图所示:


另一篇博客提到了更快的一种解法

你可能感兴趣的:(leetcoce)