我为下面复制的Leetcode问题编写了以下解决方案:
编写一个程序,通过填充空白单元格来解决数独难题。
数独解决方案必须满足以下所有规则:
每个数字1-9必须在每行中恰好出现一次。每个 数字1-9必须在每列中恰好出现一次。每个 数字1-9必须在以下9个3x3子框中的每一个中恰好出现一次 网格。空单元格由字符“。”指示。 注意:
给定的面板仅包含数字1-9和字符“。”。你可以 假设给定的数独谜题将具有一个唯一的 解。给定的电路板尺寸始终为9x9。
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
EMPTY_CELL = '.'
target = set(str(n) for n in range(1, 10))
def _validate():
cols = [[board[r][c] for r in range(9)] for c in range(9)]
boxes = []
for i in (0, 3, 6):
for j in (0, 3, 6):
boxes.append([board[a][b] for a in range(i, i + 3) for b in range(j, j + 3)])
valid_rows = all(set(row) == target for row in board)
valid_cols = valid_rows and all(set(col) == target for col in cols)
valid_boxes = valid_cols and all(set(box) == target for box in boxes)
return valid_boxes
def helper(r, c):
if c == len(board[0]):
return helper(r + 1, 0)
if r == len(board):
return _validate()
if not board[r][c] == EMPTY_CELL:
return helper(r, c + 1)
for n in range(1, 10):
board[r][c] = str(n)
if helper(r, c + 1):
return True
return False
helper(0, 0)
这是我用简单的英语表达的策略。对于每个空白的单元格,我尝试在该单元格中放置一个数字,然后在板子的其余部分重复计算。如果那不能导致有效的解决方案,我将回溯,增加数字,然后递归将数字放置在空单元格中。
我的validate函数将为所有内容返回False
,最后我得到一个空白处有9
的木板。该问题保证了每个测试用例都有正确的解决方案。我已经多次浏览了这段代码,无法看到问题所在。
(我知道我可以使用约束传播来加快解决方案的速度,但是当前的问题不是我的解决方案太慢-它的解决方案不正确)。
有人知道为什么吗?另外,如果从问题陈述中不清楚这一点,则每个数字都应为字符串。
答案 0 :(得分:1)
如果您向其提供正确的解决方案,您的验证函数将会返回true。您可以自己喂一个已解决的数独板来验证这一点:
solved_text = '''435269781
682571493
197834562
826195347
374682915
951743628
519326874
248957136
763418259'''
solved_board = [ list(line) for line in solved_text.split('\n') ]
有两个问题。首先,您实际上并没有搜索解决方案的完整空间。如果您打印传递到_validate()
的每个完整的木板,您会发现有些奇怪:整个木板总是按词汇顺序排列!这不是10 ^ 81组可能的板子。只需省略以下两行代码即可解决此问题:
if not board[r][c] == EMPTY_CELL:
return helper(r, c + 1)
这是造成问题的原因,因为您将板的状态改变为副作用,但是在回溯时不进行清理(放回空单元格)。您可以简单地省略这两行(以使helper()
中的算法永远不在乎(r,c)词法顺序中的右边),或者通过添加代码来设置board[r][c] = EMPTY_CELL
跟踪。
另一个问题是,由于您仅在完整的电路板上运行验证,并且由于验证功能只能检查完整的电路板的正确性,因此您的算法实际上必须先遍历所有10 ^ 81个可能的电路板,然后才能找到解决方案。这不仅缓慢,而且完全难缠!相反,您应该重写验证功能,以便它可以验证 partial 板。例如,如果第一行是['1','1','。',...,'。'],则应返回False,但是如果第一行是['1','2', '。',...,'。'],因为到目前为止没有问题,它应该返回True。显然,您现在还必须在每一步都调用_validate(),而不仅仅是在电路板完成时……但这是值得的,因为否则您将花费大量时间来探索显然永远无法工作的电路板。
在算法生效之前,您需要修复两个问题。
答案 1 :(得分:0)
您没有正确的验证!您的验证仅适用于最终解决方案。除非您尝试汇总数独的所有可能填写内容,否则此验证不会给您任何检查(并且始终为false)。
在我看来,回溯算法的伪代码如下:
因此验证不是要检查所有行,列,框是否都具有1到9,而是要检查它们是否没有重复!在python代码中,这意味着len(x) == len(set(x))
代表x
的行,列或框,仅占用“ 1”到“ 9”,而不占用“。”。