思路1
遍历棋盘中的每一个位置,对于空位置,把1-9都往里面填,假设当前填入1没有打破条件(横向没有重复的点,纵向没有重复的点,方格里也没重复的点),那么当前位置就填1,再找下一个空位置。
如果发现空位置找不到可以填入的元素时,就逐一往回倒退,把先前填入的元素都改回空,代码中体现在
if self.solve(board):
return True
else:
board[i][j] = "."
完整代码如下
class Solution:
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: void Do not return anything, modify board in-place instead.
"""
self.solve(board)
def solve(self,board):
for i in range(9):
for j in range(9):
if board[i][j] == ".":
for candidate in range(1,10):
if self.isValid(board, i, j, str(candidate)):
board[i][j] = str(candidate)
if self.solve(board):
return True
else:
board[i][j] = "."
return False
return True
def isValid(self, board, i, j, candidate):
for index in range(9):
if board[index][j] == candidate or board[i][index] == candidate:
return False
for row in range(3*(i//3), 3*(i//3)+3):
for col in range(3*(j//3), 3*(j//3)+3):
if board[row][col] == candidate:
return False
return True
注意这里的回退的写法,好好品味,可以避免很多深拷贝的操作
思路2
基于思路1 的回退法,再加上一些改进,模拟人的正常思维。
我们玩数独的时候,不会从第一个空位置开始,考虑其所有的可能情况然后遍历去填入。
我们会选一个空位置,这个空位置的候选集最少,从这个候选集最少的位置开始填。
比如红色的位置,候选集有(1 2 4),而绿色的位置,候选集只有一个(4),那么该位置就唯一确定了。每次循环中,我们总是选择一个候选集最小的位置,这样可以避免大量无用的递归。
基于这个思想我们可以写出如下代码,用一个字典存放横向、纵向和方格中的候选集,并在每次填完空位时进行删除更新,发现无路可走是再进行回退更新。
代码如下
class Solution:
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: void Do not return anything, modify board in-place instead.
"""
dictionary = {}
dictionary["row"] = {}
dictionary["column"] = {}
dictionary["grid"] = {}
for category in ["row","column","grid"]:
for i in range(9):
dictionary[category][i] = set(range(1,10))
for i in range(9):
for j in range(9):
if board[i][j] != ".":
number = int(board[i][j])
dictionary["row"][i].remove(number)
dictionary["column"][j].remove(number)
dictionary["grid"][3*(i//3)+j//3].remove(number)
self.slove(board,dictionary)
def slove(self,board,dictionary):
miner = float("inf")
candidates = None
for i in range(9):
for j in range(9):
if board[i][j] == ".":
row_cdt = dictionary["row"][i]
col_cdt = dictionary["column"][j]
grid_cdt = dictionary["grid"][3*(i//3)+j//3]
cross_cdt = row_cdt & col_cdt & grid_cdt
if len(cross_cdt) ==0:
return False
if miner > len(cross_cdt):
miner = len(cross_cdt)
candidates = cross_cdt
for candidate in candidates:
board[i][j] = str(candidate)
# print(candidate)
dictionary["row"][i].remove(candidate)
dictionary["column"][j].remove(candidate)
dictionary["grid"][3*(i//3)+j//3].remove(candidate)
if self.slove(board, dictionary):
return True
else:
board[i][j] = "."
dictionary["row"][i].add(candidate)
dictionary["column"][j].add(candidate)
dictionary["grid"][3*(i//3)+j//3].add(candidate)
return False
return True
可以看到优化后的递归比原先的递归快了将近一倍