版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MyCodecOdecoDecodE/article/details/78314280
LeetCode题目:37. Sudoku Solver
原题链接:https://leetcode.com/problems/sudoku-solver/description/
解题思路:
(现实生活中都不会怎么解数独,怎么写代码= =,于是用比较暴力的方法实现了。)
解数独分为两部分:
1.利用唯一余数法解出答案
同列,同行,同宫出现的数字排除后,该格只能填一个数,即为答案。
2.递归猜测答案
如果已经不存在1中的方格,选择一个方格递归所有可能填的值,直到得出答案。
核心思想:
唯一取余法思想:
用bitset数组(二进制流数组)储存某一行,某一列或者某一宫已出现的数字,每一个二进制位代表某个数字是否出现过。
所以,某一格ij能填写的数字为 (第i行的二进制流 | 第j列的二进制流 | 第t宫的二进制流) 0的位置,其中t=i / 3 * 3 + j / 3
遍历数独所有方格,当一个方格只有唯一解的时候,填上答案,修改bitset数组。
扫描二维码关注公众号,回复: 2990783 查看本文章用2的方法递归同行,同列,同宫的所有方格。如果用唯一余数法没有找到可填写的数字,则此时整个数独已经没有能填写的方格。此时只能递归猜测答案。
递归猜测答案思想:
基本情况:
如果出现没有解的可填写方格,则这种猜测失败。
如果所有方格都填写上了解,则猜测成功,当前矩阵即为答案,把当前矩阵返回上一层。
递归情况:
用唯一取余法填写已经确定的方格,直到所有方格都无法确定。
任取一未填写方格,分别递归所有可能的取值,并得到返回值,如果出现返回值为true,代表已经找到答案,返回答案矩阵。
代码细节:
- 第i行j列的方格在i / 3 * 3 + j / 3宫中
- 猜测的时候,复制所有环境进行下一轮递归,如果得到正确答案,将复制值代替原始值即可。
- 为了简化复杂度,选出可能性最少的方格再进行猜测填写。
坑点:
- 光使用唯一余数法并不能解出答案。
代码:
// #include <bitset>
// 从左往右,从上到下分为9宫,在这里为0-8
int getSquareNumber(int i, int j) {
return i / 3 * 3 + j / 3;
}
// 得到i,j位方格的Bitset
bitset<9> getSquareBitset(int i, int j, bitset<9>* row, bitset<9>* col, bitset<9>* squ) {
return row[i] | col[j] | squ[getSquareNumber(i, j)];
}
// 判断该位置是否只有唯一解
bool hasUniqueAnswer(int i, int j, bitset<9>* row, bitset<9>* col, bitset<9>* squ) {
return (getSquareBitset(i, j, row, col, squ)).count() == 8;
}
// 判断该位置是否无解
bool hasNoAnswer(int i, int j, bitset<9>* row, bitset<9>* col, bitset<9>* squ) {
return (getSquareBitset(i, j, row, col, squ)).count() == 9;
}
// 返回当前数独的状态,-1代表已经无解,0代表可能有解,1代表已经解出
int getProblemState(vector<vector<char>>& board, bitset<9>* row,
bitset<9>* col, bitset<9>* squ) {
int state = 1;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
// 若不存在'.',意为解出答案
if (board[i][j] == '.') {
state = 0;
// 如果有一个'.'无解,则无解
if (hasNoAnswer(i, j, row, col, squ))
return -1;
}
}
}
return state;
}
// 根据数独设置rows,cols,squs和remainEmptyNum
void setEnvironment(vector<vector<char>>& board, bitset<9>* row,
bitset<9>* col, bitset<9>* squ, int &remainEmptyNum) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
remainEmptyNum++;
continue;
}
int num = board[i][j] - '1';
// 设置行
row[i].set(num);
// 设置列
col[j].set(num);
// 设置宫
squ[getSquareNumber(i, j)].set(num);
}
}
}
// 填充唯一解的方格,并递归寻找同行,同列,同宫的方格
void recurSolveUniqueAnswer(vector<vector<char>>& board, int i, int j,
bitset<9>* row, bitset<9>* col, bitset<9>* squ) {
int seqIndex = getSquareNumber(i, j);
bitset<9> temp = row[i] | col[j] | squ[seqIndex];
// 查找该值
int t;
for (t = 0; t < 9; t++)
if (!temp.test(t)) break;
// 修改bitsets,board,remainEmptyNum
row[i].set(t);
col[j].set(t);
squ[seqIndex].set(t);
board[i][j] = t + '1';
// 递归求解
for (t = 0; t < 9; t++) {
// 同行递归
if (board[i][t] == '.' && hasUniqueAnswer(i, t, row, col, squ))
recurSolveUniqueAnswer(board, i, t, row, col, squ);
// 同列递归
if (board[t][j] == '.' && hasUniqueAnswer(t, j, row, col, squ))
recurSolveUniqueAnswer(board, t, j, row, col, squ);
// 同宫递归
int rowIndex = seqIndex / 3 * 3 + t / 3;
int colIndex = seqIndex % 3 * 3 + t % 3;
if (board[rowIndex][colIndex] == '.' && hasUniqueAnswer(rowIndex, colIndex, row, col, squ))
recurSolveUniqueAnswer(board, rowIndex, colIndex, row, col, squ);
}
}
// 递归求解问题,并返回是否有解
bool recurSolveProblem(vector<vector<char>>& board,
bitset<9>* row, bitset<9>* col, bitset<9>* squ) {
// 找出是否存在唯一解,若有,进行求解并填充
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (board[i][j] == '.' && hasUniqueAnswer(i, j, row, col, squ))
recurSolveUniqueAnswer(board, i, j, row, col, squ);
// 基本情况:已经出现结果
int state = getProblemState(board, row, col, squ);
if (state != 0) return state == 1;
// 递归情况:找到最少可能性的方格对所有可能性进行递归
// 找到最小可能性的方格
int r, c;
bitset<9> minBitset;
minBitset.set();
bitset<9> temp;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
temp = getSquareBitset(i, j, row, col, squ);
if (temp.count() < minBitset.count()) {
minBitset = temp;
r = i;
c = j;
}
}
}
}
// 对其中可能出现的情况依次递归
for (int i = 0; i < 9; i++) {
if (!minBitset.test(i)) {
// 复制初始环境
vector<vector<char>> cboard = board;
bitset<9> crow[9], ccol[9], csqu[9];
for (int i = 0; i < 9; i++) {
crow[i] = row[i];
ccol[i] = col[i];
csqu[i] = squ[i];
}
// 假设该位填写数字(i + 1)
crow[r].set(i);
ccol[c].set(i);
csqu[getSquareNumber(r, c)].set(i);
cboard[r][c] = i + '1';
// 递归解出题目
if (recurSolveProblem(cboard, crow, ccol, csqu)) {
// 返回true,并复制答案
board.clear();
board = cboard;
return true;
}
}
}
// 遍历所有可能都没有得出答案,则错误发生在之前的步骤
return false;
}
void solveSudoku(vector<vector<char>>& board) {
int remainEmptyNum; // 剩下未填写的格子
bitset<9> row[9], col[9], squ[9]; // 用于表示第i行,第i列,第i宫已填写数字情况
// 第j个bit为1,代表数字j已填写
// 初始化条件
setEnvironment(board, row, col, squ, remainEmptyNum);
// 递归寻找解
recurSolveProblem(board, row, col, squ);
}