编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

一个数独。

答案被标成红色。

Note:

  • 给定的数独序列只包含数字 1-9 和字符 '.'
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

解题思路

这个之前的问题Leetcode 51:N皇后(最详细的解法!!!)其实是一样的,都是通过回溯法来解。我们就要判断我们每次摆放数字是不是有效,我们只要进行如下三种判断即可

  • 所有行是不是满足条件
  • 所有列是不是满足条件
  • 当前的小正方型是不是满足条件

我们定义函数isValid(row, col, c)用于判断row行上是不是有字符c,判断col列上是不是有字符crowcol所在的小正方形内是不是有c

这里唯一需要注意的就是小正方形内的坐标变换非常需要技巧,参考Leetcode 36:有效的数独(超详细的解法!!!)中的处理思路,每个小方格看成(row//3,col//3),然后小方格内的每个元素坐标就是(row//3*3+i//3,col//3*3+i%3)

def isValid(self, board, row, col, c):for i in range(9):if board[i][col] == c or board[row][i] == c or\board[row//3*3+i//3][col//3*3+i%3] == c:return Falsereturn True

接着我们只要从到位遍历board,如果当前的位置不是"."的话,我们就一次放入"123456789"挨个尝试。

class Solution:def solveSudoku(self, board):""":type board: List[List[str]]:rtype: void Do not return anything, modify board in-place instead."""for i in range(9):for j in range(9):if board[i][j] != '.':continuefor c in "123456789":if self.isValid(board, i, j, c):board[i][j] = c        if self.solveSudoku(board):return Trueboard[i][j] = '.'return Falsereturn Truedef isValid(self, board, row, col, c):for i in range(9):if board[i][col] == c or board[row][i] == c or\board[row//3*3+i//3][col//3*3+i%3] == c:return Falsereturn True

上面这个算法对于一般的应试来说是足够了的,但是这种思路不够快。那么对于这种dfs的问题,一个比较好的策略就是开始的搜索范围最好尽量小(对于这个问题中就是尽量优先填充有较少选择的点),实际上我们在解数独问题的时候也是使用的这种策略。

那么如何确定哪些点选择较少呢?我们可以通过row[x]记录x未出现的数,通过col[y]记录y列未出现的数,通过cell[x//3][y//3]记录(x,y)对应小正方形内未出现的数,那么当前数的可选范围就是(row[x]&col[y]&cell[x//3][y//3])

那么现在要怎么处理rowcolcell的存储呢?因为要使用交集,所以很显然使用set这个结构(当然也可以使用位运算)。

from collections import defaultdict
class Solution:def solveSudoku(self, b: List[List[str]]) -> None:"""Do not return anything, modify board in-place instead."""U = set([str(i) for i in range(1, 10)])row, col, cell = defaultdict(set), defaultdict(set), defaultdict(set)def get(i, j):return (U-row[i]) & (U-col[j]) & (U-cell[i//3,j//3])def dfs(cur):if not cur:return Trueminv, x, y = 10, 0, 0for i in range(9): # 每次寻找最少的搜索范围for j in range(9):if b[i][j] == '.':tc = len(get(i, j))if len(get(i, j)) < minv:minv, x, y = tc, i, jfor i in get(x, y):row[x].add(i)col[y].add(i)cell[x//3,y//3].add(i)b[x][y] = iif dfs(cur - 1):return Truerow[x].remove(i)col[y].remove(i)cell[x//3,y//3].remove(i)b[x][y] = '.'return Falsefor i in range(9):#计算每行、每列、每个单元格的数字个数for j in range(9):if b[i][j] != '.':row[i].add(b[i][j])col[j].add(b[i][j])cell[i//3,j//3].add(b[i][j])dfs(res)

上面的代码思路很清晰,比之前的版本快了十几倍,但是依旧不是最好的写法。

我们可以将上面代码中每次寻找最少的搜索范围进行优化,可以通过一个结构存储每个'.'的可填充数。每次递归的时候查找最少的可搜索元素minv,然后将该位置删除(因为这个位置将要填充元素)。接着遍历最少的可搜索元素minv中的每个数i,删除存储结构中横纵坐标或者小方格坐标和删除位置相同的元素(因为相同的横纵坐标和小方格内不能再使用该数)。

from collections import defaultdict
class Solution:def solveSudoku(self, b: List[List[str]]) -> None:"""Do not return anything, modify board in-place instead."""U = set([str(i) for i in range(1, 10)])row, col, cell, cnt = defaultdict(set), defaultdict(set), defaultdict(set), defaultdict(set)def get(i, j):return (U-row[i]) & (U-col[j]) & (U-cell[i//3,j//3])def dfs(cur):if not cur:return Truex, y = min(cnt, key=lambda n: len(cnt[n]))# 计算最少的搜索范围minv = cnt[x,y]del cnt[x,y]for i in minv:to_update = []for k, v in cnt.items(): #删除其他位置与之重叠的元素if i in v and (k[0] == x or k[1] == y or \(x//3, y//3) == (k[0]//3, k[1]//3)):v.remove(i)to_update.append(k)b[x][y] = iif dfs(cur - 1):return Trueb[x][y] = '.'for pos in to_update:cnt[pos].add(i)cnt[x,y] = minvreturn Falsefor i in range(9):for j in range(9):if b[i][j] != '.':row[i].add(b[i][j])col[j].add(b[i][j])cell[i//3,j//3].add(b[i][j])res = 0for i in range(9):for j in range(9):if b[i][j] == '.':cnt[i,j] = get(i, j)res += 1dfs(res)

这个代码的效率比前一个版本又快了一倍,非常优秀!。

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

Leetcode 37:解数独(超详细的解法!!!)相关推荐

  1. Java实现 LeetCode 37 解数独

    37. 解数独 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实 ...

  2. LeetCode—37. 解数独(困难)

    37. 解数独(困难) 题目描述: 编写一个程序,通过填充空格来解决数独问题. 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 ...

  3. LintCode 802. 数独(回溯)/ LeetCode 37. 解数独

    1. 题目 编写一个程序,通过填充空单元来解决数独难题. 空单元由数字0表示. 你可以认为只有一个唯一的解决方案. LeetCode 37 题类似,把 int 改成 char,注意转换 2. 解题 行 ...

  4. leetcode 37. 解数独 思考分析

    目录 题目 核心思路的不断细化 1.核心框架 2.考虑到每个位置的工作 3.考虑到到达最后一列.该位置的数已经预置的情况 4.判断是否符合规则的函数 5.确定递归终止条件+确定函数返回值 AC代码 题 ...

  5. LeetCode 37. 解数独

    一.题目描述 编写一个程序,通过填充空格来解决数独问题. 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 ...

  6. LeetCode 37 解数独

    题目描述 编写一个程序,通过填充空格来解决数独问题.一个数独的解法需遵循如下规则:数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 3x ...

  7. LeetCode 37. 解数独 Sudoku Solver

    编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 3x3 ...

  8. 递归生成数独java_[leetcode] 37. 解数独(Java)(dfs,递归,回溯)

    1A 这个题其实15分钟左右就敲出来并且对了...但是由于我输错了一个数..导致我白白debug一个多小时.. 没啥难度,练递归-dfs的好题 class Solution { private int ...

  9. Leetcode算法Java全解答--37. 解数独

    Leetcode算法Java全解答–37. 解数独 文章目录 Leetcode算法Java全解答--37. 解数独 题目 想法 结果 总结 代码 我的答案 大佬们的答案 测试用例 其他 题目 编写一个 ...

最新文章

  1. split()的使用
  2. Windows 开机自启Web服务
  3. 基于MATLAB的面向对象编程(4)——类文件
  4. Windows server 2003 CA配置(一)
  5. html菜鸟ruby,Ruby 循环
  6. 怎样高效入门 Vue?
  7. VoltDB公布4.0版本号,大步提高内存实时分析速度,进军操作数据库市场
  8. oracle 表空间异常增长过快解决方法
  9. linux (fedora 28) 制作启动U盘,启动盘
  10. 8-3 redis sentine
  11. 如何确定C语言中数组的大小?
  12. UltraISO 对光盘镜像常用操作方法图解
  13. network attached storage 的简称,中文称为网络附加存储
  14. 怎样在线完成视频转gif制作?一招视频转gif在线制作
  15. SSRS 锁定标题栏
  16. tiup telemetry
  17. 删除电脑上重复备份的图片
  18. Spring-全面详解(基础知识)
  19. yang模型中rpc_RPC校正方法研究
  20. (二)admin-boot项目之整合mybatis-plus

热门文章

  1. 恒生关闭HOMS系统开立功能
  2. 与socket网络编程有关的函数
  3. 读取 DTC 信息服务 (0x19) – UDS 协议
  4. 关于多标签分类任务的损失函数和评价指标的一点理解
  5. 实现原理 扫描枪_手持式RFID扫描器、条码扫描枪设计(原理图、PCB、源码及论文)...
  6. C语言函数大全--g开头的函数(下)
  7. vs2019 中文离线安装包下载,类似ISO
  8. 腾讯云代理商:共青城市与“腾讯云”举行战略合作协议远程签约仪式
  9. 记一次 Java 进程里面获取 Mysql 连接超时的问题排查(OOM)
  10. 计算机毕业设计ssm木棉堂水果电商平台1r83i系统+程序+源码+lw+远程部署