回溯法是解标准数独最直接有效的方法,需严格验证每行、每列、每宫格的合法性,剪枝关键在于每次填数前调用isvalid函数检查三重约束,且坐标映射、类型转换和循环变量命名须精准无误。

回溯法是解数独最直接有效的手段
标准数独(9×9)的约束明确:每行、每列、每个3×3宫格都必须填入1–9且不重复。这种强约束天然适合回溯——尝试填一个数,检查是否合法;不合法就撤回,换下一个;合法则递归推进到下一个空格。暴力枚举虽理论上可行(9^81),但剪枝后实际运行极快。
关键不是“能不能解”,而是“怎么剪枝”。每次填数前必须验证:isValid(board, row, col, num)——它要同时检查行、列、宫格三个维度。漏掉任一检查,就会得到错误解或无限递归。
- 推荐用
vector<vector>></vector>存储棋盘,空格用'.'表示 - 找下一个空格时,别用双重循环每次都从 (0,0) 开始扫描;记录上一次位置或预存所有空格坐标,避免重复遍历
- 数字填入和撤销必须成对出现:
board[row][col] = '1' + num;→ 递归 →board[row][col] = '.';
isValid 函数最容易写错的三处细节
isValid 看似简单,但实际调试中最常暴露逻辑漏洞。尤其注意坐标映射和类型转换:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 宫格索引计算不能写成
(row / 3) * 3 + col / 3—— 这是扁平化索引,而你需要的是宫格起始坐标:boxRow = (row / 3) * 3,boxCol = (col / 3) * 3,再用两层for遍历该宫格 - 比较时务必统一类型:
board[i][j] == '0' + num是错的,因为num是整数 0–8,应写为board[i][j] == '1' + num - 检查行/列时,循环变量别和外层冲突;常见错误是把
for (int i = 0; i 写成 <code>for (int row = 0; row ,导致参数 <code>row被覆盖
如何避免栈溢出或超时
递归深度最大为81层,C++默认栈空间通常够用,但若开启大量调试符号或在嵌入式环境运行,仍可能触发 std::stack_overflow 或 TLE。真正瓶颈往往不在递归深度,而在无效状态膨胀:
- 不要在每次调用
isValid时重新遍历整行整列——提前维护三个布尔数组:rowUsed[9][10]、colUsed[9][10]、boxUsed[9][10],填数时同步更新,可将单次验证降到 O(1) - 空格顺序影响很大:优先选择候选数字最少的位置(MRV 启发式)。可先扫描一遍,用
vector<tuple>>></tuple>记录每个空格及其可行数字,再按长度排序 - 一旦找到第一个解就立即返回 true 并终止所有递归分支;别用 void 函数+全局标志,容易漏 break 或多填
输入输出格式与 LeetCode 兼容性要点
多数在线判题平台(如 LeetCode #37)要求原地修改 vector<vector>>& board</vector>,且不返回值。这意味着你不能新建 board 或依赖函数返回值判断成功与否:
- 递归函数签名建议为
bool solve(vector<vector>>& board)</vector>,成功返回 true 并隐式修改 board;失败返回 false - 主函数中调用后无需再处理返回值,board 已就位。但要注意:如果题目保证有唯一解,你不必处理无解情况;若需支持无解输入,得确保回溯能干净退出
- 字符比较区分大小写:
'.' != '0',输入里空格一定是点号,别误判为 ASCII 0
边界情形比如全满棋盘或零空格,solve 应直接返回 true。真正麻烦的是那些看似有解、实则矛盾的输入——这时候回溯会自然穷尽所有分支后返回 false,不需要额外校验。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










