数独系列算法

目录

一、问题背景:数独的规则与挑战

二、有效的数独:高效验证算法

问题分析

算法实现

算法复杂度

三、解数独:回溯算法的经典应用

问题分析

算法实现

算法复杂度

四、两种算法的关联与区别

五、总结


数独作为经典的逻辑谜题,不仅是休闲益智的选择,也蕴含了丰富的算法设计思路。本文将围绕 "有效的数独" 验证和 "解数独" 两个问题,深入讲解其背后的算法逻辑与实现细节。

一、问题背景:数独的规则与挑战

数独是一个 9×9 的网格,需满足以下规则:

  • 数字 1-9每一行只能出现一次;
  • 数字 1-9每一列只能出现一次;
  • 数字 1-9每一个 3×3 子网格(共 9 个)内只能出现一次。

我们要解决两个核心问题:

  1. 有效的数独:验证已填入的数字是否符合规则(无需保证数独可解);
  2. 解数独:填充所有空格,得到一个符合规则的完整数独。

二、有效的数独:高效验证算法

问题分析

我们需要快速判断每一行、每一列、每一个 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),实际远低于此
应用场景 快速排查数独的合法性 需得到完整解的场景

五、总结

"有效的数独" 和 "解数独" 是数独问题的两个核心方向,分别体现了哈希表验证回溯剪枝的经典算法思想。

  • 对于 "有效性验证",利用三个数组记录行、列、子网格的数字出现情况,可在常数时间内完成判断;
  • 对于 "解数独",回溯算法是最直接的思路,通过 "尝试 - 验证 - 回溯" 的流程,逐步填充所有空格。

这两个问题的解法也为其他类似的 "约束满足问题"(如八皇后、迷宫求解)提供了参考,掌握它们的思路,能帮助你在更多算法场景中举一反三。

相关推荐
liebe1*18 小时前
C语言程序代码(四)
c语言·数据结构·算法
进击的圆儿8 小时前
递归专题4 - 网格DFS与回溯
数据结构·算法·递归回溯
程序猿20239 小时前
Python每日一练---第一天:买卖股票的最佳时机
算法
夏鹏今天学习了吗9 小时前
【LeetCode热题100(56/100)】组合总和
算法·leetcode·职场和发展
ZPC82109 小时前
opencv 获取图像中物体的坐标值
人工智能·python·算法·机器人
颇有几分姿色9 小时前
密码学算法分类指南
算法·密码学
绝无仅有10 小时前
某游戏大厂的 Redis 面试必问题解析
后端·算法·面试
微笑尅乐10 小时前
三种方法解开——力扣3370.仅含置位位的最小整数
python·算法·leetcode
MMjeaty10 小时前
查找及其算法
c++·算法