【LeetCode刷题日记】37.解数独

发布时间:2026/7/6 8:10:02
【LeetCode刷题日记】37.解数独 个人主页代码不加冰欢迎来访作者简介java后端学习者❄️个人专栏LeetCode刷题日记 苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言hello大家好我是代码不加冰广东这边天天下雨而且都是一阵一阵的早上想出去都出不去实在是没啥办法先刷一道算法题吧让我们一起来看看。摘要本文介绍了使用回溯算法解决数独问题的思路。数独需满足每行、每列及3x3宫格内数字1-9不重复。通过递归遍历棋盘每个空格尝试填入1-9并验证合法性若失败则回溯。关键点包括使用布尔返回值快速终止递归、二维遍历确定位置、九宫格起始索引计算row/33和col/33。算法时间复杂度高但能保证找到唯一解体现了回溯试错-回溯的核心思想与N皇后问题类似但约束条件更复杂。文末提供了完整的Java实现代码。题目背景37.解数独编写一个程序通过填充空格来解决数独问题。数独的解法需遵循如下规则数字1-9在每一行只能出现一次。数字1-9在每一列只能出现一次。数字1-9在每一个以粗实线分隔的3x3宫内只能出现一次。请参考示例图数独部分空格内已填入了数字空白格用.表示。示例 1输入board [[5,3,.,.,7,.,.,.,.],[6,.,.,1,9,5,.,.,.],[.,9,8,.,.,.,.,6,.],[8,.,.,.,6,.,.,.,3],[4,.,.,8,.,3,.,.,1],[7,.,.,.,2,.,.,.,6],[.,6,.,.,.,.,2,8,.],[.,.,.,4,1,9,.,.,5],[.,.,.,.,8,.,.,7,9]]输出[[5,3,4,6,7,8,9,1,2],[6,7,2,1,9,5,3,4,8],[1,9,8,3,4,2,5,6,7],[8,5,9,7,6,1,4,2,3],[4,2,6,8,5,3,7,9,1],[7,1,3,9,2,4,8,5,6],[9,6,1,5,3,7,2,8,4],[2,8,7,4,1,9,6,3,5],[3,4,5,2,8,6,1,7,9]]解释输入的数独如上图所示唯一有效的解决方案如下所示提示board.length 9board[i].length 9board[i][j]是一位数字或者.题目数据保证输入数独仅有一个解题目解析上一篇文章我们总结过一些题型是专门用回溯算法来做的解数独就包含在内因此整体的思路就是回溯算法的模板。然后我们读完题目发现这道题和前面的N皇后有点异曲同工之妙都是利用递归寻找不合适就回溯然后对元素进行检查是否符合。都是有一些限制的只不过这道题限制可能更多一些。我们细致分析一下这道题的逻辑N皇后是因为每一行每一列只放一个皇后只需要一层for循环遍历一行递归来遍历列然后一行一列确定皇后的唯一位置。本题就不同了本题中棋盘的每一个位置都要放一个数字而N皇后是一行只放一个皇后并检查数字是否合法解数独的树形结构要比N皇后更宽更深。整体的图示之后我们进行代码逻辑的拆解回溯三部曲递归函数以及参数递归函数的返回值需要是bool类型为什么呢因为解数独找到一个符合的条件就在树的叶子节点上立刻就返回相当于找从根节点到叶子节点一条唯一路径所以需要使用bool返回值。代码如下bool backtracking(vectorvectorchar board)递归终止条件本题递归不用终止条件解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。不用终止条件会不会死循环递归的下一层的棋盘一定比上一层的棋盘多一个数等数填满了棋盘自然就终止填满当然好了说明找到结果了所以不需要终止条件那么有没有永远填不满的情况呢这个问题在递归单层搜索逻辑里再来讲递归单层搜索逻辑在树形图中可以看出我们需要的是一个二维的递归 一行一列一个for循环遍历棋盘的行一个for循环遍历棋盘的列一行一列确定下来之后递归遍历这个位置放9个数字的可能性代码如下bool backtracking(vectorvectorchar board) { for (int i 0; i board.size(); i) { // 遍历行 for (int j 0; j board[0].size(); j) { // 遍历列 if (board[i][j] ! .) continue; for (char k 1; k 9; k) { // (i, j) 这个位置放k是否合适 if (isValid(i, j, k, board)) { board[i][j] k; // 放置k if (backtracking(board)) return true; // 如果找到合适一组立刻返回 board[i][j] .; // 回溯撤销k } } return false; // 9个数都试完了都不行那么就返回false } } return true; // 遍历完没有返回false说明找到了合适棋盘位置了 }注意这里return false的地方这里放return false 是有讲究的。因为如果一行一列确定下来了这里尝试了9个数都不行说明这个棋盘找不到解决数独问题的解那么会直接返回这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去判断棋盘是否合法判断棋盘是否合法有如下三个维度同行是否重复同列是否重复9宫格里是否重复continue的作用continue会跳过当前循环的剩余代码直接进入下一次迭代。在这个代码中java for (int j 0; j 9; j) { if (board[i][j] ! .) { continue; // 跳过本次循环执行 j } // 下面的代码只有在 board[i][j] . 时才会执行 for (char k 1; k 9; k) { // 尝试填数... } }关于九宫格其实就是排除相同元素只是要找到九个格子的其实位置也就是起始索引用于循环遍历startRow 对照表row值row/3startRow所属宫格行0,1,200第0行宫格3,4,513第1行宫格6,7,826第2行宫格startCol 对照表col值col/3startCol所属宫格列0,1,200第0列宫格3,4,513第1列宫格6,7,826第2列宫格组合示例text 格子 (2, 1)startRow0, startCol0 → 宫格0 格子 (3, 4)startRow3, startCol3 → 宫格4 格子 (7, 8)startRow6, startCol6 → 宫格8 格子 (5, 3)startRow3, startCol3 → 宫格4注意和上面的格子不同参数列表参数类型含义示例值rowint要检查的格子所在行号0-82colint要检查的格子所在列号0-85valchar要尝试填入的数字1-97boardchar[][]当前数独棋盘board题目答案class Solution { public void solveSudoku(char[][] board) { solveSudokuHelper(board); } private boolean solveSudokuHelper(char[][] board){ //「一个for循环遍历棋盘的行一个for循环遍历棋盘的列 // 一行一列确定下来之后递归遍历这个位置放9个数字的可能性」 for (int i 0; i 9; i){ // 遍历行 for (int j 0; j 9; j){ // 遍历列 if (board[i][j] ! .){ // 跳过原始数字 continue; } for (char k 1; k 9; k){ // (i, j) 这个位置放k是否合适 if (isValidSudoku(i, j, k, board)){ board[i][j] k; if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回 return true; } board[i][j] .; } } // 9个数都试完了都不行那么就返回false return false; // 因为如果一行一列确定下来了这里尝试了9个数都不行说明这个棋盘找不到解决数独问题的解 // 那么会直接返回 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去」 } } // 遍历完没有返回false说明找到了合适棋盘位置了 return true; } /** * 判断棋盘是否合法有如下三个维度: * 同行是否重复 * 同列是否重复 * 9宫格里是否重复 */ private boolean isValidSudoku(int row, int col, char val, char[][] board){ // 同行是否重复 for (int i 0; i 9; i){ if (board[row][i] val){ return false; } } // 同列是否重复 for (int j 0; j 9; j){ if (board[j][col] val){ return false; } } // 9宫格里是否重复 int startRow (row / 3) * 3; int startCol (col / 3) * 3; for (int i startRow; i startRow 3; i){ for (int j startCol; j startCol 3; j){ if (board[i][j] val){ return false; } } } return true; } }结语我们的回溯章节就告一段段落了刚开始的时候感觉难以理解其实整体上都是一个模板没必要太过于害怕。