Leetcode 37:解数独(超详细的解法!!!)
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
Note:
- 给定的数独序列只包含数字
1-9
和字符'.'
。 - 你可以假设给定的数独只有唯一解。
- 给定数独永远是
9x9
形式的。
解题思路
这个之前的问题Leetcode 51:N皇后(最详细的解法!!!)其实是一样的,都是通过回溯法来解。我们就要判断我们每次摆放数字是不是有效,我们只要进行如下三种判断即可
- 所有行是不是满足条件
- 所有列是不是满足条件
- 当前的小正方型是不是满足条件
我们定义函数isValid(row, col, c)
用于判断row
行上是不是有字符c
,判断col
列上是不是有字符c
,row
和col
所在的小正方形内是不是有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])
。
那么现在要怎么处理row
、col
和cell
的存储呢?因为要使用交集
,所以很显然使用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:解数独(超详细的解法!!!)相关推荐
- Java实现 LeetCode 37 解数独
37. 解数独 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实 ...
- LeetCode—37. 解数独(困难)
37. 解数独(困难) 题目描述: 编写一个程序,通过填充空格来解决数独问题. 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 ...
- LintCode 802. 数独(回溯)/ LeetCode 37. 解数独
1. 题目 编写一个程序,通过填充空单元来解决数独难题. 空单元由数字0表示. 你可以认为只有一个唯一的解决方案. LeetCode 37 题类似,把 int 改成 char,注意转换 2. 解题 行 ...
- leetcode 37. 解数独 思考分析
目录 题目 核心思路的不断细化 1.核心框架 2.考虑到每个位置的工作 3.考虑到到达最后一列.该位置的数已经预置的情况 4.判断是否符合规则的函数 5.确定递归终止条件+确定函数返回值 AC代码 题 ...
- LeetCode 37. 解数独
一.题目描述 编写一个程序,通过填充空格来解决数独问题. 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 ...
- LeetCode 37 解数独
题目描述 编写一个程序,通过填充空格来解决数独问题.一个数独的解法需遵循如下规则:数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 3x ...
- LeetCode 37. 解数独 Sudoku Solver
编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实线分隔的 3x3 ...
- 递归生成数独java_[leetcode] 37. 解数独(Java)(dfs,递归,回溯)
1A 这个题其实15分钟左右就敲出来并且对了...但是由于我输错了一个数..导致我白白debug一个多小时.. 没啥难度,练递归-dfs的好题 class Solution { private int ...
- Leetcode算法Java全解答--37. 解数独
Leetcode算法Java全解答–37. 解数独 文章目录 Leetcode算法Java全解答--37. 解数独 题目 想法 结果 总结 代码 我的答案 大佬们的答案 测试用例 其他 题目 编写一个 ...
最新文章
- split()的使用
- Windows 开机自启Web服务
- 基于MATLAB的面向对象编程(4)——类文件
- Windows server 2003 CA配置(一)
- html菜鸟ruby,Ruby 循环
- 怎样高效入门 Vue?
- VoltDB公布4.0版本号,大步提高内存实时分析速度,进军操作数据库市场
- oracle 表空间异常增长过快解决方法
- linux (fedora 28) 制作启动U盘,启动盘
- 8-3 redis sentine
- 如何确定C语言中数组的大小?
- UltraISO 对光盘镜像常用操作方法图解
- network attached storage 的简称,中文称为网络附加存储
- 怎样在线完成视频转gif制作?一招视频转gif在线制作
- SSRS 锁定标题栏
- tiup telemetry
- 删除电脑上重复备份的图片
- Spring-全面详解(基础知识)
- yang模型中rpc_RPC校正方法研究
- (二)admin-boot项目之整合mybatis-plus
热门文章
- 恒生关闭HOMS系统开立功能
- 与socket网络编程有关的函数
- 读取 DTC 信息服务 (0x19) – UDS 协议
- 关于多标签分类任务的损失函数和评价指标的一点理解
- 实现原理 扫描枪_手持式RFID扫描器、条码扫描枪设计(原理图、PCB、源码及论文)...
- C语言函数大全--g开头的函数(下)
- vs2019 中文离线安装包下载,类似ISO
- 腾讯云代理商:共青城市与“腾讯云”举行战略合作协议远程签约仪式
- 记一次 Java 进程里面获取 Mysql 连接超时的问题排查(OOM)
- 计算机毕业设计ssm木棉堂水果电商平台1r83i系统+程序+源码+lw+远程部署