数独问题
力扣原题链接
问题描述
数独的解法需遵循如下规则:
- 数字 1-9 在每一行只能出现一次。
- 数字 1-9 在每一列只能出现一次。
- 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用'.'
表示。
示例
示例:
输入: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"]]
解题思路
我们可以使用回溯算法来解决数独问题。
- 遍历数独中的每一个格子,如果格子为空白,就尝试填入数字 1-9,然后检查填入后是否合法。
- 若填入的数字合法,则继续递归地填充下一个格子。
- 若填入的数字不合法,则回溯到上一个状态,尝试其他的数字。
- 当遍历到最后一个格子时,如果所有的格子都已经被填满且合法,则找到了一个解决方案。
复杂度分析
- 时间复杂度:回溯算法的时间复杂度取决于数独的解的数量。对于 9x9 的数独,一般情况下有多个解,因此时间复杂度是指数级别的。
- 空间复杂度:O(1),除了结果列表之外,主要消耗的是递归调用栈的空间。
好的,下面是这个代码的算法步骤演示:
算法步骤演示
-
初始化
- 创建一个函数
solveSudoku
,该函数接受一个二维字符数组board
作为参数。 - 在
solveSudoku
函数中调用backtrack
函数,并将board
作为参数传递给它。
- 创建一个函数
-
回溯函数
- 回溯函数
backtrack
接受一个二维字符数组board
作为参数,它用于填充数独的空白格子。 - 遍历数独的每一个格子:
- 如果当前格子为空白
'.'
,则尝试填入数字'1'
到'9'
。 - 对于每一个尝试填入的数字,检查该数字是否合法(满足数独规则):
- 检查当前行是否包含目标字符。
- 检查当前列是否包含目标字符。
- 检查当前 3x3 宫是否包含目标字符。
- 如果该数字合法,则将其填入当前格子,并递归调用
backtrack
函数,尝试填充下一个格子。 - 如果递归调用返回
true
,表示找到了一个解决方案,直接返回true
。 - 如果递归调用返回
false
,表示当前尝试不成功,回溯到上一个状态,继续尝试其他的数字。
- 如果当前格子为空白
- 如果遍历完所有的格子都成功填充,说明找到了一个解决方案,返回
true
。 - 如果没有找到解决方案,返回
false
。
- 回溯函数
-
检查数字合法性的函数
- 检查数字合法性的函数
isvalid
接受一个二维字符数组board
、要填入的数字c
、以及目标格子的行坐标row
和列坐标col
作为参数。 - 首先根据目标格子的位置计算出当前 3x3 宫的左上角的行坐标和列坐标。
- 然后分别检查当前行、当前列以及当前 3x3 宫是否包含目标数字
c
。 - 如果不包含目标数字,则返回
true
,否则返回false
。
- 检查数字合法性的函数
算法实现
class Solution {// 解决数独问题的入口方法public void solveSudoku(char[][] board) {backtrack(board);}// 回溯函数,用于填充数独格子boolean backtrack(char[][] board) {// 遍历数独格子for (int i = 0; i < board.length; i++) {for (int j = 0; j < board.length; j++) {// 如果当前格子为空白if (board[i][j] == '.') {// 尝试填入数字 '1' 到 '9'for (char k = '1'; k <= '9'; k++) {// 如果当前填入的数字符合数独规则if (isvalid(board, k, i, j)) {// 填入数字并递归调用回溯函数board[i][j] = k;if (backtrack(board)) return true;// 如果递归调用返回 false,说明填入当前数字不行,回溯到上一步board[i][j] = '.';}}// 如果尝试了所有数字都不行,返回 falsereturn false;}}}// 如果所有格子都已填满,返回 truereturn true;}// 检查当前填入的数字是否符合数独规则boolean isvalid(char[][] board, char c, int row, int col) {// 计算当前格子所在的 3x3 宫的左上角的行坐标和列坐标int startRow = row - row % 3;int startCol = col - col % 3;// 检查当前行是否包含目标字符for (int i = 0; i < board.length; i++) {if (board[row][i] == c) return false;}// 检查当前列是否包含目标字符for (int i = 0; i < board.length; i++) {if (board[i][col] == c) return false;}// 检查当前 3x3 宫是否包含目标字符for (int i = startRow; i < startRow + 3; i++) {for (int j = startCol; j < startCol + 3; j++) {if (board[i][j] == c) return false;}}// 如果所有检查都通过,返回 truereturn true;}
}