首页 简历|笔试面试

37.解数独

  • 25年9月4日 发布
  • 129.81KB 共6页
37.解数独37.解数独37.解数独37.解数独37.解数独

37. 解数独

鲁迅说过:世界就是一个巨大的草台班子,那就让我们先从二哥的 LeetCode 刷

题笔记开始吧!

题意

编写一个程序,通过填充空格来解决数独问题。

数独的解法需遵循如下规则:

• 数字 1-9 在每一行只能出现一次。

• 数字 1-9 在每一列只能出现一次。

• 数字 1-9 在每一个以粗实线分隔的 3x3 九宫格内只能出现一次。

数独的部分空格内已填入了数字,空白格用 ‘.’ 表示。

难度

困难

示例

037.解数独-20240801155636.png

输入: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"]]

解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

037.解数独-20240801155716.png

分析

解数独,通常会出现在一些趣味杂志或者团建活动上,最朴素最暴力的解法就是去猜一个

数,然后判断其符不符合要求,符合就去猜下一个数。

计算机在面对这类问题的时候,因为处理速度比人脑快得多多多多,所以可以挨个枚举每

个 “坑” 应该填写的数字,然后再检验一下符不符合数独的要求,就可以得出答案了。

题目要求保证唯一解,所以我们需要记录每一个 . 的位置(也就是空白格的位置),然后

逐个枚举合法的数字,确定与当前的每一行、每一列、每个九宫格都没有冲突。

如果没有冲突,就去枚举下一个,如果有冲突的话,就表示之前猜想的数字有问题,我们

可以直接回溯到之前的状态,更换新的数字。

记住一点,就是我们回溯的时候……

必须要把状态恢复到之前的样子!

必须要把状态恢复到之前的样子!

必须要把状态恢复到之前的样子!

因为之后的操作,和上次失败的操作,在逻辑关系上是并行的。

如果没有回溯到失败之前的状态,直接向下执行,那么之前失败的状态就会对接下来的判

断造成影响,逻辑关系就有问题了。

具体的思路如下:

1. 从左到右,从上到下遍历数独板。

2. 对于每一个空格,尝试填充数字 1-9。

3. 检查填充的数字是否符合数独规则。

4. 如果填充有效,则递归处理下一个空格;如果无效,回溯(撤销填充),尝试下一个

数字。

5. 如果所有空格都填充完毕,则数独解答成功。

class Solution {

public void solveSudoku(char[][] board) {

solve(board);

}

private boolean solve(char[][] board) {

for (int i = 0; i < 9; i++) {

for (int j = 0; j < 9; j++) {

if (board[i][j] == '.') { // 发现空格

for (char c = '1'; c <= '9'; c++) { // 尝试填充数字 '1' 到 '9'

if (isValid(board, i, j, c)) { // 检查数字是否符合规则

board[i][j] = c; // 填充数字

if (solve(board)) { // 递归处理下一个空格

return true;

} else {

board[i][j] = '.'; // 回溯,撤销填充

}

}

}

return false; // 如果所有数字都不符合规则,返回 false

}

}

}

return true; // 所有空格填充完毕,返回 true

}

private boolean isValid(char[][] board, int row, int col, char c) {

for (int i = 0; i < 9; i++) {

// 检查当前行是否有重复

if (board[row][i] == c) return false;

// 检查当前列是否有重复

if (board[i][col] == c) return false;

// 检查当前 3x3 宫格是否有重复

if (board[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3] == c) return false;

}

return true; // 数字符合规则

}

}

其中回溯的关键步骤就这两行代码:

else {

board[i][j] = '.'; // 回溯,撤销填充

}

考虑一个简单的数独局部:

53..7....

6..195...

.98....6.

假设在位置 (0, 2) 我们尝试填入数字 4。如果后续无法找到有效的解,我们需要撤销这个

填充(即将 (0, 2) 恢复为 .),然后尝试填入其他数字。如果没有撤销步骤,位置 (0, 2)

会一直保持为 4,影响后续的尝试。

来看一下执行效率:

037.解数独-20240801162035.png

总结

本题主要就是考察回溯算法的应用,回溯算法是一种递归算法,通过不断地尝试,找到所

有的解。在这个过程中,我们需要记录每一步的状态,以便在尝试失败的时候,回溯到之

前的状态。

至于知识点,还是 Java 中的二维数组、循环遍历等。

哦,如果还有的话,就是九宫格的下标计算方法:

board[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3]

我们将 9x9 数独板分为 9 个 3x3 的小宫格,每个小宫格的索引可以通过下面的方式计

算:

(0, 0), (0, 1), (0, 2) | (0, 3), (0, 4), (0, 5) | (0, 6), (0, 7), (0, 8)

(1, 0), (1, 1), (1, 2) | (1, 3), (1, 4), (1, 5) | (1, 6), (1, 7), (1, 8)

(2, 0), (2, 1), (2, 2) | (2, 3), (2, 4), (2, 5) | (2, 6), (2, 7), (2, 8)

-----------------------+-------------------------+-------------------------

(3, 0), (3, 1), (3, 2) | (3, 3), (3, 4), (3, 5) | (3, 6), (3, 7), (3, 8)

(4, 0), (4, 1), (4, 2) | (4, 3), (4, 4), (4, 5) | (4, 6), (4, 7), (4, 8)

(5, 0), (5, 1), (5, 2) | (5, 3), (5, 4), (5, 5) | (5, 6), (5, 7), (5, 8)

-----------------------+-------------------------+-------------------------

(6, 0), (6, 1), (6, 2) | (6, 3), (6, 4), (6, 5) | (6, 6), (6, 7), (6, 8)

(7, 0), (7, 1), (7, 2) | (7, 3), (7, 4), (7, 5) | (7, 6), (7, 7), (7, 8)

(8, 0), (8, 1), (8, 2) | (8, 3), (8, 4), (8, 5) | (8, 6), (8, 7), (8, 8)

• 对于给定的行索引 row 和列索引 col,我们可以通过 row / 3 和 col / 3 分别确定它们

所在的 3x3 宫格。

• row / 3 * 3 得到 3x3 宫格的行起点。

• col / 3 * 3 得到 3x3 宫格的列起点。

• 再使用一个辅助变量 i,其取值范围为 0 到 8,用于遍历 3x3 宫格中的所有位置。

• i / 3 和 i % 3:分别计算 3x3 宫格内部的行和列偏移量。

假设我们正在检查元素 (4, 5) 是否符合数独规则:

• row = 4,col = 5

• row / 3 * 3 = 1 * 3 = 3:起始行索引是 3

• col / 3 * 3 = 1 * 3 = 3:起始列索引是 3`

通过遍历 i 从 0 到 8,可以得到 3x3 宫格中的所有元素位置:

• 当 i = 0:(3 + 0 / 3, 3 + 0 % 3) = (3, 3)

• 当 i = 1:(3 + 1 / 3, 3 + 1 % 3) = (3, 4)

• 当 i = 2:(3 + 2 / 3, 3 + 2 % 3) = (3, 5)

• …

• 当 i = 8:(3 + 8 / 3, 3 + 8 % 3) = (5, 5)

这样,我们就可以遍历当前元素所在的整个 3x3 宫格,并检查是否有重复的数字。

力扣链接:https://leetcode.cn/problems/sudoku-solver/

开通会员 本次下载免费

所有资料全部免费下载! 推荐用户付费下载获取返佣积分! 积分可以兑换商品!
一键复制 下载文档 联系客服