填写数独板-回溯解决方案问题

时间:2019-04-26 19:57:30

标签: python algorithm backtracking

我为下面复制的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的木板。该问题保证了每个测试用例都有正确的解决方案。我已经多次浏览了这段代码,无法看到问题所在。

(我知道我可以使用约束传播来加快解决方案的速度,但是当前的问题不是我的解决方案太慢-它的解决方案不正确)。

有人知道为什么吗?另外,如果从问题陈述中不清楚这一点,则每个数字都应为字符串。

2 个答案:

答案 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. 从单元格(0,0)扫描到单元格(8,8),找到一个空的单元格
  2. 测试选项“ 1”到“ 9”
    1. 对每个选项的调用验证(如果有效),请返回到上面的扫描行
    2. 如果验证失败,请尝试其他选项
    3. 如果用尽所有选项“ 1”到“ 9”,则先前的递归级别无效,请尝试另一个

因此验证不是要检查所有行,列,框是否都具有1到9,而是要检查它们是否没有重复!在python代码中,这意味着len(x) == len(set(x))代表x的行,列或框,仅占用“ 1”到“ 9”,而不占用“。”。