【Leetcode】37. Sudoku Solver

本文介绍两种数独求解算法,一是基于回溯法的基本思路,二是在此基础上加入候选集优化,选择候选数最少的位置填充,减少无效递归,提高求解效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路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

可以看到优化后的递归比原先的递归快了将近一倍
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值