目录
数独作为经典的逻辑谜题,不仅是休闲益智的选择,也蕴含了丰富的算法设计思路。本文将围绕 "有效的数独" 验证和 "解数独" 两个问题,深入讲解其背后的算法逻辑与实现细节。
一、问题背景:数独的规则与挑战
数独是一个 9×9 的网格,需满足以下规则:
- 数字
1-9在每一行只能出现一次; - 数字
1-9在每一列只能出现一次; - 数字
1-9在每一个 3×3 子网格(共 9 个)内只能出现一次。
我们要解决两个核心问题:
- 有效的数独:验证已填入的数字是否符合规则(无需保证数独可解);
- 解数独:填充所有空格,得到一个符合规则的完整数独。
二、有效的数独:高效验证算法

问题分析
我们需要快速判断每一行、每一列、每一个 3×3 子网格内的数字是否唯一。最直观的思路是用三个数组分别记录行、列、子网格的数字出现情况。
算法实现

cpp
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
// 三个数组分别记录:行、列、3×3子网格的数字出现情况
bool rows[9][10] = {false}; // rows[i][num]:第i行是否出现过num
bool cols[9][10] = {false}; // cols[j][num]:第j列是否出现过num
bool grid[3][3][10] = {false};// grid[x][y][num]:第(x,y)个3×3子网格是否出现过num
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') continue; // 空格跳过
int num = board[i][j] - '0';
// 若行、列或子网格中已出现该数字,直接返回无效
if (rows[i][num] || cols[j][num] || grid[i/3][j/3][num]) {
return false;
}
// 标记该数字已出现
rows[i][num] = cols[j][num] = grid[i/3][j/3][num] = true;
}
}
return true;
}
};
算法复杂度
- 时间复杂度:O(1)(因为网格固定为 9×9,循环次数是常数);
- 空间复杂度:O(1)(三个数组的大小固定)。
三、解数独:回溯算法的经典应用

问题分析
解数独需要填充所有空格 ,且每一步都要满足数独规则。这类 "尝试所有可能并回溯" 的问题,最适合用深度优先搜索(DFS)+ 回溯来解决。
算法实现

cpp
class Solution {
public:
// 三个数组记录行、列、3×3子网格的数字使用情况
bool row[9][10] = {false}, col[9][10] = {false}, grid[3][3][10] = {false};
void solveSudoku(vector<vector<char>>& board) {
// 初始化:标记已存在的数字
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] != '.') {
int num = board[i][j] - '0';
row[i][num] = col[j][num] = grid[i/3][j/3][num] = true;
}
}
}
dfs(board); // 开始回溯求解
}
bool dfs(vector<vector<char>>& board) {
// 遍历每一个格子
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') { // 找到空格,尝试填入数字
for (int num = 1; num <= 9; ++num) {
// 检查该数字是否可用(行、列、子网格都未出现)
if (!row[i][num] && !col[j][num] && !grid[i/3][j/3][num]) {
board[i][j] = num + '0'; // 填入数字
row[i][num] = col[j][num] = grid[i/3][j/3][num] = true; // 标记为已使用
if (dfs(board)) { // 递归求解下一个空格,若成功则返回true
return true;
}
// 回溯:撤销填入的数字,尝试下一个可能
board[i][j] = '.';
row[i][num] = col[j][num] = grid[i/3][j/3][num] = false;
}
}
return false; // 所有数字都尝试过,无法填入,回溯
}
}
}
return true; // 所有空格都填满,数独已解
}
};
算法复杂度
- 时间复杂度:最坏情况下为 O(981)(每个空格有 9 种可能,共 81 个空格),但实际中由于数独规则的约束,回溯会提前剪枝,效率远高于理论上界;
- 空间复杂度:O(1)(数组大小固定)+ 递归栈深度 O(81)(最多递归 81 层)。
四、两种算法的关联与区别
| 维度 | 有效的数独 | 解数独 |
|---|---|---|
| 目标 | 验证已填数字是否合法 | 填充所有空格,得到合法数独 |
| 算法核心 | 遍历 + 哈希表(记录数字出现情况) | 回溯 + 剪枝(尝试所有可能并回退) |
| 时间复杂度 | O(1)(固定 9×9 网格) | 最坏 O(981),实际远低于此 |
| 应用场景 | 快速排查数独的合法性 | 需得到完整解的场景 |
五、总结
"有效的数独" 和 "解数独" 是数独问题的两个核心方向,分别体现了哈希表验证 和回溯剪枝的经典算法思想。
- 对于 "有效性验证",利用三个数组记录行、列、子网格的数字出现情况,可在常数时间内完成判断;
- 对于 "解数独",回溯算法是最直接的思路,通过 "尝试 - 验证 - 回溯" 的流程,逐步填充所有空格。
这两个问题的解法也为其他类似的 "约束满足问题"(如八皇后、迷宫求解)提供了参考,掌握它们的思路,能帮助你在更多算法场景中举一反三。