编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
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 == 9
board[i].length == 9
board[i][j]
是一位数字或者 '.'
本题跟上一道题N皇后还不一样,数独比N皇后还要复杂一些,因为数独要做的是二维递归。
什么是二维递归?
N皇后问题 是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。
本题就不一样了,本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
代码如下:(详细看注释)
def backtracking(self, board):
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] != '.': # 如果当前位置已经有数字,则跳过
continue
for k in range(1, 10): # 尝试填入数字1到9
if self.is_valid(i, j, k, board): # 检查当前数字是否合法
board[i][j] = str(k) # 填入数字
if self.backtracking(board): # 递归尝试填入下一个位置
return True # 如果成功找到解,则返回True
board[i][j] = '.' # 回溯,将当前位置重置为'.'
return False # 如果所有数字都尝试过都不合法,则返回False
return True # 如果所有位置都填满了且合法,则返回True
注意这里return false的地方,这里放return false 是有讲究的。
因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
那么会直接返回, 这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!
判断棋盘是否合法有如下三个维度:
代码如下:
def is_valid(self, row, col, val, board):
for i in range(len(board[0])):
if board[row][i] == str(val): # 检查同一行是否有重复数字
return False
for i in range(len(board)):
if board[i][col] == str(val): # 检查同一列是否有重复数字
return False
start_i = (row // 3) * 3 # 找到当前单元格所在的小九宫格的起始行
start_j = (col // 3) * 3 # 找到当前单元格所在的小九宫格的起始列
for i in range(start_i, start_i + 3):
for j in range(start_j, start_j + 3):
if board[i][j] == str(val): # 检查小九宫格内是否有重复数字
return False
return True # 如果没有重复数字,则返回True
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
不返回任何内容,直接修改原棋盘
"""
self.backtracking(board) # 调用回溯算法
def backtracking(self, board):
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] != '.': # 如果当前位置已经有数字,则跳过
continue
for k in range(1, 10): # 尝试填入数字1到9
if self.is_valid(i, j, k, board): # 检查当前数字是否合法
board[i][j] = str(k) # 填入数字
if self.backtracking(board): # 递归尝试填入下一个位置
return True # 如果成功找到解,则返回True
board[i][j] = '.' # 回溯,将当前位置重置为'.'
return False # 如果所有数字都尝试过都不合法,则返回False
return True # 如果所有位置都填满了且合法,则返回True
def is_valid(self, row, col, val, board):
for i in range(len(board[0])):
if board[row][i] == str(val): # 检查同一行是否有重复数字
return False
for i in range(len(board)):
if board[i][col] == str(val): # 检查同一列是否有重复数字
return False
start_i = (row // 3) * 3 # 找到当前单元格所在的小九宫格的起始行
start_j = (col // 3) * 3 # 找到当前单元格所在的小九宫格的起始列
for i in range(start_i, start_i + 3):
for j in range(start_j, start_j + 3):
if board[i][j] == str(val): # 检查小九宫格内是否有重复数字
return False
return True # 如果没有重复数字,则返回True