题干:
https://leetcode.cn/problems/sudoku-solver/
- 使用“有效的数独”中的函数,代码如下:
class Solution {
private:
bool answer{ false };//用来递归时,判断是否找到了答案
public:
//自己写的检验数独合法的代码效率似乎有点低,频繁调用影响有点大
//isValidSudoku(),这是借用官方的代码
bool isValidSudoku(vector<vector<char>>& board) {
int rows[9][9];
int columns[9][9];
int subboxes[3][3][9];
memset(rows, 0, sizeof(rows));
memset(columns, 0, sizeof(columns));
memset(subboxes, 0, sizeof(subboxes));
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
int index = c - '0' - 1;
rows[i][index]++;
columns[j][index]++;
subboxes[i / 3][j / 3][index]++;
if (rows[i][index] > 1 || columns[j][index] > 1 || subboxes[i / 3][j / 3][index] > 1) {
return false;
}
}
}
}
return true;
}
//解数独的函数
void solveSudoku(vector<vector<char>>& board) {
backTracking(0, 0, board);
}
//递归的函数
//对 board[row][col] 位置,插入数字(参数的解释)
void backTracking(int row,int col,vector<vector<char>>& board) {
while (row != 9 && board[row][col] != '.') {
row = row + (col + 1) / 9;
col = (col + 1) % 9;
}//找空格(如果递归完了,row可能会 == 9,所以退出递归的条件放在后面)
if (row == 9) {
answer = true;
return;
}//找到了答案,退出递归的条件
for (char i = '1'; i <= '9'; i++)//对 board[row][col] 位置,填入数字
{
board[row][col] = i;
if (isValidSudoku(board)) {
backTracking(row + (col + 1) / 9, (col + 1) % 9, board);//填入i合法,递归转向下一格
}
else {
continue;
}
//重点:回溯
if (answer) return;//如果归来时已经找到了答案,就不用继续递归寻找答案(不能放在for头部,否则 i==9 归来时如果已经找到了答案,如果不在这里return,退出循环了会被置为空格)
}
//重点:回溯
board[row][col] = '.';//9归来时都没有找到答案,说明这个节点的1-9都不行,恢复为空,返回上一格
return;
}
};
- 上面的代码效率其实不算太高,原因在于每一次填入数字时都调用了 isValidSudoku() 函数判断是否合法。这导致了两个缺点:1、频繁调用该函数,对 isValidSudoku() 函数的效率比较敏感;2、在某个位置填入数字后,没有必要检验整个数独,只需要检验该位置所在行、列、和九宫格。代码如下:
class Solution {
private:
bool answer{ false };//用来递归时,判断是否找到了答案
public:
//判断是否满足数独条件
bool isvalid(int row, int col, vector<vector<char>>& board) {
unordered_set<char> hashSet_row{};//判断行的哈希表
unordered_set<char> hashSet_col{};//判断列的哈希表
unordered_set<char> hashSet_box{};//判断九宫格的哈希表
for (int i = 0; i < board.size(); i++) {
if (board[row][i] != '.') {
if (hashSet_row.find(board[row][i]) == hashSet_row.end()) //判断行符合
{
hashSet_row.insert(board[row][i]);
}
else { return false; }
}
if (board[i][col] != '.') {
if (hashSet_col.find(board[i][col]) == hashSet_col.end()) //判断列符合
{
hashSet_col.insert(board[i][col]);
}
else { return false; }
}
}
//判断九宫格符合
//(row/3)*3,(col/3)*3是所在九宫格的起始位置
int rowStart = (row / 3) * 3;
int colStart = (col / 3) * 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[rowStart + i][colStart + j] != '.') {
if (hashSet_box.find(board[rowStart + i][colStart + j]) == hashSet_box.end()) {
hashSet_box.insert(board[rowStart + i][colStart + j]);
}
else { return false; }
}
}
}
//经过了考验
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backTracking(0, 0, board);
}
//对 board[row][col] 位置,插入数字
void backTracking(int row, int col, vector<vector<char>>& board) {
while (row != 9 && board[row][col] != '.') {
row = row + (col + 1) / 9;
col = (col + 1) % 9;
}//找到空格
if (row == 9) {
answer = true;
return;
}//找到了答案,退出递归的条件
for (char i = '1'; i <= '9'; i++)//填入数字,合法就转向下一格
{
board[row][col] = i;
if (isvalid(row, col, board)) {
backTracking(row + (col + 1) / 9, (col + 1) % 9, board);
}
else {
continue;
}
//重点:回溯
if (answer) return;//如果归来时已经找到了答案,就不用继续递归寻找答案(不能放在for头部,否则9归来时如果已经找到了答案,退出循环会被置为空格)
}
//重点:回溯
board[row][col] = '.';//9归来时都没有找到答案,说明这个节点的1-9都不行,恢复为空,返回上一格
return;
}
};
- 似乎使用了哈希表 unordered_set ,效率会降低。真是难受啊。对于判断数独是否符合条件,还是用数组吧…
class Solution {
private:
bool answer{ false };//用来递归时,判断是否找到了答案
public:
//判断填入数字后数独是否合法(默认初始数独合法)
bool isvalid(int row, int col, vector<vector<char>>& board) {
//数组模拟哈希表
char arrRow[10]{ 0 };//判断行的数组,arrRow[k] 保存 字符'k'出现的次数
char arrCol[10]{ 0 };//判断列的数组,arrCol[k] 保存 字符'k'出现的次数
char arrBox[10]{ 0 };//判断行的数组,arrBox[k] 保存 字符'k'出现的次数
for (int i = 0; i < board.size(); i++) {
//判断行符合
if (board[row][i] != '.') {
int index = board[row][i] - '0';//index 对于字符 board[row][i] 在数组中的下标
if (arrRow[index] == 0) {
arrRow[index] = 1;
}
else { return false; }
}
//判断列符合
if (board[i][col] != '.') {
int index = board[i][col] - '0';//index 对于字符 board[i][col] 在数组中的下标
if (arrCol[index] == 0) {
arrCol[index] = 1;
}
else { return false; }
}
}
//判断九宫格符合
//(row/3)*3,(col/3)*3是所在九宫格的起始位置
int rowStart = (row / 3) * 3;
int colStart = (col / 3) * 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[rowStart + i][colStart + j] != '.') {
int index = board[rowStart + i][colStart + j] - '0';//index 对于字符 board[rowStart + i][colStart + j] 在数组中的下标
if (arrBox[index] == 0) {
arrBox[index] = 1;
}
else { return false; }
}
}
}
//经过了考验
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backTracking(0, 0, board);
}
//对 board[row][col] 位置,插入数字
void backTracking(int row, int col, vector<vector<char>>& board) {
while (row != 9 && board[row][col] != '.') {
row = row + (col + 1) / 9;
col = (col + 1) % 9;
}//找到空格,最后row可能出界了,所以递归退出条件放在下面
if (row == 9) {
answer = true;
return;
}//找到了答案,退出递归的条件
for (char i = '1'; i <= '9'; i++)//填入数字,合法就转向下一格
{
board[row][col] = i;
if (isvalid(row, col, board)) {
backTracking(row + (col + 1) / 9, (col + 1) % 9, board);
}
else {continue;}
//重点:回溯
if (answer) return;//如果归来时已经找到了答案,就不用继续递归寻找答案(不能放在for头部,否则9归来时如果已经找到了答案,退出循环会被置为空格)
}
//重点:回溯
board[row][col] = '.';//9归来时都没有找到答案,说明这个节点的1-9都不行,恢复为空,返回上一格
return;
}
};