LeetCode> 37. 解数独 (回溯法)
题目
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。
示例:
输入:board = [["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],
["6","7","2","1","9","5","3","4","8"],
["1","9","8","3","4","2","5","6","7"],
["8","5","9","7","6","1","4","2","3"],
["4","2","6","8","5","3","7","9","1"],
["7","1","3","9","2","4","8","5","6"],
["9","6","1","5","3","7","2","8","4"],
["2","8","7","4","1","9","6","3","5"],
["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
提示:
- board.length == 9
- board[i].length == 9
- board[i][j] 是一位数字或者 '.'
- 题目数据 保证 输入数独仅有一个解
解析
思路:迭代回溯法
用一个动态数组/列表p[]存放要填入数据的空格信息,p的第i个元素p[i][x,y,v]代表矩阵位置x行y列填入数据v。
sum记录方案个数。
下面来套用标准的回溯法。
// 空格信息数组,三元组包含空格行号、列号、元素值信息
vector<tuple<int,int,char>> p;
// 方案数
int sum;
void init() {
sum = 0;
p初始化为包含空格信息的数组;
...
}
void backtrace(vector &board, int k) {
if (k >= p.size && sum == 0) { // 空格元素一共只有size个,迭代到size+1个意味着所有空格已经成功填完数字
sum=1; // 已经找到一种方案
}
else {
for 字符i = 字符1..9 // 要填写的数字字符 {
// 检查通过,才填写元素
if check(board, k, i) { 检查空格k是否能写字符i,如果能,则填写
place(board, k, i); // 空格k放置元素i
backtrace(board, k+1); // 迭代到空格k+1
if sum > 0 // 已经找到一种方案,不需要恢复board 以继续搜索其他方案
return;
unplace(board, k, i); // 恢复空格k放置的元素i
}
}
}
}
// 检查空格k写元素c是否合理
// 检查同行、同列、同宫格(3x3小矩阵)内是否有相同元素
bool check(vector &board, int k, char c) {
}
// 空格k写元素c
void place(vector &board, int k, char c)) {
}
// 撤销空格k写元素c,恢复之前的'.'
void unplace(vector &board, int k, char c)) {
}
优化:每次要为空格k填入元素,而检查是否可行时,如果循环遍历每行、每列、每个宫格元素,会做很多无用查询。为了加快check检查速度,可以用3个9x9矩阵用于存放已经每行、每列、每个宫格字符1~9出现次数。(因为每个数字最多出现1次,也可以使用bool类型)。
另外,放置数据和恢复数据时,统计次数的数组值也要及时更新。
int rowcnt[9][9]; // 每行所包含的字符1~9的次数
int colcnt[9][9]; // 每列所包含的字符1~9的次数
int cubecnt[9][9]; // 每个宫格所包含的字符1~9的次数
// 求宫格编号
inline int getcubeid(int x, int y) {
return (x / 3) * 3 + y / 3;
}
init, place, unplace略
源代码
class Solution {
public:
void print(vector<vector<char>> &board) {
for (int i = 0; i < board.size(); ++i) {
for (int j = 0; j < board[i].size(); ++j) {
cout << board[i][j];
if (j < board[i].size() - 1) cout << " ";
else cout << endl;
}
}
}
bool check(vector<vector<char>>& board, int k, char c) {
if (c < '1' || c > '9') {
std::cout << "illegal place value" << endl;
return false;
}
int x = get<0>(p[k]);
int y = get<1>(p[k]);
int v = c - '1';
// 检查同一列
if (colcnt[y][v] > 0) return false;
// 检查同一行
if (rowcnt[x][v] > 0) return false;
// 检查3x3宫格
if (cubecnt[(x / 3) * 3 + y / 3][v] > 0) return false;
return true;
}
/**
* 空格k 放置字符c
*/
void place(vector<vector<char>>& board, int k, char c) {
int x = get<0>(p[k]);
int y = get<1>(p[k]);
// 填入数字i
get<2>(p[k]) = c;
board[x][y] = c;
int v = c - '1';
rowcnt[x][v]++;
colcnt[y][v]++;
cubecnt[(x/3)*3 + y/3][v]++;
}
/**
* 恢复空格k 放置字符c前状态
*/
void unplace(vector<vector<char>>& board, int k, char c) {
int x = get<0>(p[k]);
int y = get<1>(p[k]);
// 恢复填入的数字前状态
get<2>(p[k]) = '.';
board[x][y] = '.';
int v = c - '1';
rowcnt[x][v]--;
colcnt[y][v]--;
cubecnt[(x/3)*3 + y/3][v]--;
}
/**
* 迭代回溯法求出1个解
* @param k 空格k,通过空格数组p[k]访问
*/
void backtrace(vector<vector<char>>& board, int k) {
if (k >= p.size() && sum == 0) {
sum++;
// print(board);
}
else {
// 空格k依次填入1..9
for (char i = '1'; i <= '9'; ++i) {
if (check(board, k, i)) {
place(board, k, i);
backtrace(board, k + 1);
if (sum > 0) return ;
unplace(board, k, i);
}
}
}
}
void init(vector<vector<char>>& board) {
sum = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
rowcnt[i][j] = 0;
colcnt[i][j] = 0;
cubecnt[i][j] = 0;
}
}
// 记录空格位置信息, 统计元素出现次数信息
for (int i = 0; i < board.size(); ++i) {
for (int j = 0; j < board[i].size(); ++j) {
if (board[i][j] == '.')
p.push_back(make_tuple(i, j, board[i][j]));
else {
int value = board[i][j] - '1';
if (value < 0 || value >= 9) { // 确保数字一定是1~9, 对应value范围0~8
std::cerr << "input vector include illegal value at (" << i << ", " << j << ")" << endl;
exit(1);
}
rowcnt[i][value]++;
colcnt[j][value]++;
cubecnt[(i / 3) * 3 + j / 3][value]++;
}
}
}
}
// 提供的入口
void solveSudoku(vector<vector<char>>& board) {
if (board.empty()) return;
init(board);
if (p.empty()) {
cout << "the martix has been a sudoku" << endl;
return ;
}
backtrace(board, 0); // 从空格0开始回溯
}
private:
vector<tuple<int, int, char>> p; // 空格数组,包含了行号、列号、元素值
int sum; // 方案数
int rowcnt[9][9]; // (0~8)每行出现数字1~9计数
int colcnt[9][9]; // (0~8)每列出现数字1~9计数
int cubecnt[9][9]; // 每个宫格出现数字1~9计数
};
来源:37. 解数独 | leetcode
类似题目:
HJ44 数独(Sudoku)| 牛客网