https://leetcode.cn/problems/sudoku-solver/
这道题是关于编写程序解决数独问题,以下是详细思路:
一、理解数独规则
- 行规则:数字1 - 9在每一行只能出现一次。这意味着在填写数独时,每行的九个格子中不能有重复的数字。
- 列规则:数字1 - 9在每一列只能出现一次。同理,每列的九个格子也不能有重复数字。
- 宫规则:数字1 - 9在每一个以粗实线分隔的3x3宫内只能出现一次。整个数独盘面被分成了九个3x3的小宫格,每个宫内的数字也不能重复。
二、解题方法(编程角度)
- 数据结构选择
- 可以使用二维数组来表示数独盘面,例如
int[][] sudoku = new int[9][9];
,数组中的每个元素表示对应位置的数字,空白格用特定值(如0或题目中的.
)表示。
- 可以使用二维数组来表示数独盘面,例如
- 算法思路
- 回溯法 :这是解决数独问题常用的方法。
- 从数独盘面的第一个空白格开始,尝试填入1 - 9中的一个数字,然后检查该数字是否满足行、列和宫的规则。
- 如果满足规则,继续处理下一个空白格;如果不满足,就回溯到上一个空白格,尝试其他数字。
- 重复这个过程,直到所有空白格都被填满且满足数独规则。
- 优化 :
- 在尝试填入数字时,可以先根据当前已有的数字,确定每个空白格可能填入的数字范围,减少不必要的尝试。例如,某一行已经有了1、2、3、4、5,那么这一行的空白格就只能填6、7、8、9,这样可以提高算法效率。
- 回溯法 :这是解决数独问题常用的方法。
三、示例分析(以给定示例1为例)
- 首先观察示例中已有的数字,比如第一行有5、3、7,那么这一行剩下的空白格就不能再填这三个数字。
- 再看第一列有5、6、8,所以第一列的空白格也不能填这三个数字。
- 对于第一个3x3宫(左上角的宫),已有5、3、7、6,那么这个宫内的空白格只能从剩下的数字中选择。
- 按照这样的思路,逐步分析每个空白格,通过回溯法不断尝试和调整,最终完成整个数独的求解。
通过以上思路和方法,就可以编写程序来解决数独问题啦。在实际编程中,还需要注意代码的细节和边界情况的处理,以确保程序的正确性和高效性。
cpp
class Solution {
bool row[9][9]; // 用于记录每行中数字 1 - 9 的使用情况
// line[i][digit]为true表示第i行已经使用了数字digit
// digit取值范围是 0 - 8,对应实际数字 1 - 9
bool col[9][9]; // 用于记录每列中数字 1 - 9 的使用情况
// column[j][digit]为true表示第j列已经使用了数字digit
bool block[3][3][9]; // 用于记录每个 3x3 的小方块中数字 1 - 9
// 的使用情况block[i / 3][j / 3][digit]为true表示第(i /
// 3, j / 3)个小方块已经使用了数字digit
bool valid; // 用于标记当前的数独解是否有效
vector<pair<int, int>> space; // 一个向量 用于存储数独中空白位置的坐标
public:
void dfs(vector<vector<char>>& board, int pos) {
if (pos == space.size()) {
valid = true;
return;
}
auto& [i, j] = space[pos]; // 获取当前要填充的空白位置的坐标(i, j);
for (int digit = 0; digit < 9 && !valid; ++digit) {
if (!row[i][digit] && !col[j][digit] &&
!block[i / 3][j / 3][digit]) {
row[i][digit] = col[j][digit] = block[i / 3][j / 3][digit] =
true;
board[i][j] =
digit + '0' +
1; // 为了将这个数字 digit 以字符形式存储回数独板 board 中
// 需要将其转换回字符 这里利用了字符编码的特性 字符 '0'
// 的 ASCII 码值是一个固定值 例如在 ASCII 码中是 48 将
// digit 加上 '0' 就可以得到对应数字字符的 ASCII 码值
// 例如 当 digit 为 0 时,digit + '0' 就得到了字符 '0' 的
// ASCII 码值 当 digit 为 1 时,digit + '0' 就得到了字符
// '1' 的 ASCII 码值 以此类推
dfs(board, pos + 1);
row[i][digit] = col[j][digit] = block[i / 3][j / 3][digit] =
false; // 恢复现场;
}
}
}
void solveSudoku(vector<vector<char>>& board) {
memset(row, false, sizeof(row));
memset(col, false, sizeof(col));
memset(block, false, sizeof(block));
valid = false;
// 通过两个嵌套的循环遍历数独板board 如果当前位置board[i][j]是空白 即==
// '.' 将其坐标(i, j)添加到spaces向量中 否则
// 将当前位置的数字转换为对应的索引digit board[i][j] - '0' - 1
// 并将该数字在相应的行 列和小方块中的使用标记设置为true
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
space.emplace_back(i, j);
} else {
int digit = board[i][j] - '0' - 1;
// 而数独中的数字是从 1 到 9
// 为了能方便地将数独中的数字与这些标记数组的索引对应起来
// 需要将字符转换后的数字再减去 1 这样 数独中的数字 1
// 就对应标记数组中的索引 0 数字 2 对应索引 1 以此类推 数字
// 9 对应索引 8
row[i][digit] = col[j][digit] = block[i / 3][j / 3][digit] =
true;
}
}
}
dfs(board, 0);
}
};