leetcode Sudoku Solver 解法优化

来源:互联网 发布:爱新觉罗赵姓 知乎 编辑:程序博客网 时间:2024/05/16 13:06

题目

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到9board[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 rowfor(int i = 0; i < 9; ++i){if(i != col && board[row][i] == board[row][col]){return false;}}//check columnfor(int i = 0; i < 9; ++i){if(i != row && board[i][col] == board[row][col]){return false;}}//check the 3*3 blockint 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;}}//elseboard[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

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


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

0 0
原创粉丝点击