【题目】
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
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]
是一位数字或者'.'
- 题目数据 保证 输入数独仅有一个解
【题目解析】
思路
解决数独问题的常用方法是使用深度优先搜索(DFS)结合回溯算法。算法的基本思路是:
- 预处理:首先遍历整个数独板,记录下所有空白格的位置,以及每行、每列和每个宫格中已经出现的数字。
- DFS与回溯:从第一个空白格开始,尝试填充1到9中尚未在当前行、列和宫格出现的数字,然后基于当前的填充继续对下一个空白格进行DFS。如果发现当前的填充无法最终解决数独,就撤销上一步的填充(即回溯),尝试另一个数字。
- 有效性检查:在每一步填充时,检查当前数字是否满足数独的条件,即当前数字是否在同行、同列或同宫格中已存在。
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
# 存储每一行、每一列、每一个宫内已经使用的数字
rows = [set() for _ in range(9)]
cols = [set() for _ in range(9)]
boxes = [[set() for _ in range(3)] for _ in range(3)]
blanks = [] # 存储空白格位置的列表
# 初始化数据结构,并记录空白格位置
for r in range(9):
for c in range(9):
if board[r][c] == ".":
blanks.append((r, c))
else:
val = board[r][c]
rows[r].add(val)
cols[c].add(val)
boxes[r // 3][c // 3].add(val)
def backtrack(index: int) -> bool:
# 如果所有空白格都填满了,返回True
if index == len(blanks):
return True
r, c = blanks[index]
# 一个小技巧:使用字符串数字集合与行、列、宫格集合的差集,来快速得到可以尝试的数字集合
candidates = {"1", "2", "3", "4", "5", "6", "7", "8", "9"} - rows[r] - cols[c] - boxes[r // 3][c // 3]
for val in candidates:
# 做选择
board[r][c] = val
rows[r].add(val)
cols[c].add(val)
boxes[r // 3][c // 3].add(val)
# 进入下一个空白格
if backtrack(index + 1):
return True
# 撤销选择
board[r][c] = "."
rows[r].remove(val)
cols[c].remove(val)
boxes[r // 3][c // 3].remove(val)
return False
backtrack(0)
执行
【总结】
数独问题的解法体现了深度优先搜索(DFS)和回溯算法在解决约束满足问题(CSP)中的应用。在编程和算法设计中,这种组合方法适用于一系列问题,尤其是那些需要遍历大量潜在选项并在特定约束下找到解的问题。这些问题包括但不限于:
- 组合问题:如N皇后问题,需要在棋盘上放置多个棋子,使它们互不攻击。
- 排列问题:需要找到一个序列的所有可能排列,如全排列问题。
- 图遍历问题:在图中找到从起点到终点的路径,同时满足特定条件。
- 分割问题:将集合或数字分割成满足特定条件的多个子集。
解决数独问题所用的算法是回溯算法,这是一种递归算法,它的执行过程是一个不断在选项树中进行深入和回溯的过程。在数独问题中,这个算法的关键点包括:
-
选择与撤销:通过在空白格中尝试不同的数字(选择),如果尝试失败,则撤销这一选择并尝试其他选项。
-
有效性检查:每次填入数字后,都要通过检查当前的行、列和宫来确保填入的数字满足数独的规则。
-
优化策略:
- 预处理:在递归之前,先收集信息,这有助于在后续步骤中减少重复的工作。
- 剪枝:在递归过程中,一旦确定某一路径不可能导致解决方案,就立即停止继续递归该路径。
- 空白格优先级:优先填充候选项最少的空白格,这有助于快速缩小搜索范围。
在编程实践中,理解和掌握DFS和回溯算法的使用对于高效解决问题至关重要。它们不仅可以帮助我们解决具体的问题,还可以提高我们的逻辑思维和问题分析能力。通过这些算法,我们能够构建一个系统的解决方案框架,逐步探索所有可能的解决路径,并找到满足所有约束的有效解。此外,优化算法的能力是提高算法性能和应对更复杂问题的关键,这需要我们在实践中不断尝试和学习。