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

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

class Solution {
public:bool isValidSudoku(vector<vector<char>>& board) {// 三个数组分别记录:行、列、3×3子网格的数字出现情况bool rows[9][10] = {false}; // rows[i][num]:第i行是否出现过numbool cols[9][10] = {false}; // cols[j][num]:第j列是否出现过numbool grid[3][3][10] = {false};// grid[x][y][num]:第(x,y)个3×3子网格是否出现过numfor (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)+ 回溯来解决。
算法实现

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)) { // 递归求解下一个空格,若成功则返回truereturn 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),实际远低于此 |
| 应用场景 | 快速排查数独的合法性 | 需得到完整解的场景 |
五、总结
“有效的数独” 和 “解数独” 是数独问题的两个核心方向,分别体现了哈希表验证和回溯剪枝的经典算法思想。
- 对于 “有效性验证”,利用三个数组记录行、列、子网格的数字出现情况,可在常数时间内完成判断;
- 对于 “解数独”,回溯算法是最直接的思路,通过 “尝试 - 验证 - 回溯” 的流程,逐步填充所有空格。
这两个问题的解法也为其他类似的 “约束满足问题”(如八皇后、迷宫求解)提供了参考,掌握它们的思路,能帮助你在更多算法场景中举一反三。
