首页 > 其他分享 >Leetcode 37. 解数独

Leetcode 37. 解数独

时间:2024-10-08 10:46:59浏览次数:6  
标签:rows blocks 37 cols spaces spaceIndex 解数 Leetcode 数独

1.题目基本信息

1.1.题目描述

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

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

  • 数字 1-9 在每一行只能出现一次。
  • 数字 1-9 在每一列只能出现一次。
  • 数字 1-9 在每一个以粗实线分隔的 3×3 宫内只能出现一次。(请参考示例图)
    数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

1.2.题目地址

https://leetcode.cn/problems/sudoku-solver/description/

2.解题方法

2.1.解题思路

回溯

2.2.解题步骤

第一步,使用数组记录每一行、每一列、每一块的各个数字的存在状态,分别记为rows、cols、blocks,其中rows[i][j]=True表示j+1数字在下标为i的行中,反之亦然,cols和blocks也同理。与此同时,使用spaces记录所有为空的点的坐标,在后续的回溯中将其遍历填充。

第二步,构建回溯函数

  • 函数传入当前处理的空坐标在spaces数组中的下标spaceIndex,返回内容为spaces[spaceIndex:]能否构建完整的合法的数独组合。
  • 回溯本质上还是递归,所以第一步判断边界条件,当spaceIndex=len(spaces)时,根据返回内容的性质,可知返回True;
  • 第二步,遍历1-9,如果当前位置的行、列、块都没有该数字,则可以将该数字填入当前的spaceIndex的位置,同时标记rows、cols、blocks该位置为True,标记完后,递归spaceIndex+1处的下一个空位置,获取spaces[spaceIndex+1:]是否能构建合法的数独,记为valid,然后将rows、cols、blocks回溯为False(回溯是为了不影响后面在spaceIndex状况的for循环),如果递归返回valid为True,则可以直接退出for循环,代表已经构建了合法的数独,直接返回valid即可;如果经过9次的for循环都没有构建合法的数独,代表spaces[spaceIndex:]不能构建合法的数独,返回False。

注意:在递归的过程中,会将board进行修改,最终的回溯退出时,构建的数独为合法数独组合,此时的board即为题解,由于题目已经声明了数独一定有解,所以spaceIndex=0时的回溯函数一定返回True

3.解题代码

Python代码

class Solution:
    # 回溯
    # 第一步,使用数组记录每一行、每一列、每一块的各个数字的存在状态,分别记为rows、cols、blocks,其中rows[i][j]=True表示j+1数字在下标为i的行中,反之亦然,cols和blocks也同理。与此同时,使用spaces记录所有为空的点的坐标,在后续的回溯中将其遍历填充。
    # 第二步,构建回溯函数
    # > 函数传入当前处理的空坐标在spaces数组中的下标spaceIndex,返回内容为spaces[spaceIndex:]能否构建完整的合法的数独组合。
    # > 回溯本质上还是递归,所以第一步判断边界条件,当spaceIndex=len(spaces)时,根据返回内容的性质,可知返回True;
    # > 第二步,遍历1-9,如果当前位置的行、列、块都没有该数字,则可以将该数字填入当前的spaceIndex的位置,同时标记rows、cols、blocks该位置为True,标记完后,递归spaceIndex+1处的下一个空位置,获取spaces[spaceIndex+1:]是否能构建合法的数独,记为valid,然后将rows、cols、blocks回溯为False(回溯是为了不影响后面在spaceIndex状况的for循环),如果递归返回valid为True,则可以直接退出for循环,代表已经构建了合法的数独,直接返回valid即可;如果经过9次的for循环都没有构建合法的数独,代表spaces[spaceIndex:]不能构建合法的数独,返回False。
    # 注意:在递归的过程中,会将board进行修改,最终的回溯退出时,构建的数独为合法数独组合,此时的board即为题解,由于题目已经声明了数独一定有解,所以spaceIndex=0时的回溯函数一定返回True
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 从spaces中第一个位置开始遍历回溯;返回spaces[spaceIndex:]能不能填充成合法的数独,并在判断过程中设置board,在合法的数独填充时退出回溯
        def backtrack(spaceIndex):
            if spaceIndex==len(spaces):
                return True
            valid=False
            for i in range(9):
                row,col=spaces[spaceIndex]
                if not rows[row][i] and not cols[col][i] and not blocks[row//3*3+col//3][i]:
                    board[row][col]=str(i+1)
                    rows[row][i]=cols[col][i]=blocks[row//3*3+col//3][i]=True
                    valid=backtrack(spaceIndex+1)
                    rows[row][i]=cols[col][i]=blocks[row//3*3+col//3][i]=False
                if valid:
                    break
            return valid
        # 第一步,记录当前数独数组的行、列、块的状态;rows[i][j]指下标为i的行的数字j+1是否存在,cols和blocks同理
        rows=[[False]*9 for _ in range(9)]
        cols=[[False]*9 for _ in range(9)]
        blocks=[[False]*9 for _ in range(9)]
        spaces=[]   # 空的位置的坐标列表
        for i in range(9):
            for j in range(9):
                if board[i][j]!=".":
                    num=int(board[i][j])
                    rows[i][num-1]=True
                    cols[j][num-1]=True
                    blocks[i//3*3+j//3][num-1]=True
                else:
                    spaces.append((i,j))
        # print(rows,"\n",cols,"\n",blocks)
        # print(spaces)
        # print(backtrack(0))
        backtrack(0)

C++代码

class Solution {

private:
    vector<vector<bool>> rows;
    vector<vector<bool>> cols;
    vector<vector<bool>> blocks;
    vector<pair<int,int>> spaces;

public:
    bool backtrack(int spaceIndex,vector<vector<char>>& board){
        if(spaceIndex==spaces.size()){
            return true;
        }
        bool valid=false;
        for(int i=0;i<9;++i){
            int row=spaces[spaceIndex].first,col=spaces[spaceIndex].second;
            if(!rows[row][i] && !cols[col][i] && !blocks[row/3*3+col/3][i]){
                board[row][col]=(i+1)+'0';
                rows[row][i]=cols[col][i]=blocks[row/3*3+col/3][i]=true;
                valid=backtrack(spaceIndex+1,board);
                rows[row][i]=cols[col][i]=blocks[row/3*3+col/3][i]=false;
            }
            if(valid)break;
        }
        return valid;
    }
    
    void solveSudoku(vector<vector<char>>& board) {
        rows=vector<vector<bool>>(9,vector<bool>(9,false));
        cols=vector<vector<bool>>(9,vector<bool>(9,false));
        blocks=vector<vector<bool>>(9,vector<bool>(9,false));
        for(int i=0;i<9;++i){
            for(int j=0;j<9;++j){
                if(board[i][j]!='.'){
                    int num=board[i][j]-'0';
                    rows[i][num-1]=true;
                    cols[j][num-1]=true;
                    blocks[i/3*3+j/3][num-1]=true;
                }else{
                    spaces.push_back(pair<int,int>(i,j));
                }
            }
        }
        backtrack(0,board);
    }
};

4.执行结果

在这里插入图片描述

标签:rows,blocks,37,cols,spaces,spaceIndex,解数,Leetcode,数独
From: https://www.cnblogs.com/geek0070/p/18451206

相关文章

  • Day 29 动态规划part02| LeetCode 62.不同路径,63.不同路径II
    62.不同路径62.不同路径classSolution{publicintuniquePaths(intm,intn){int[][]dp=newint[m][n];//dp数组--dp[i][j]达到坐标(i,j)有几种路径//dp数值初始化--终点为:dp[m-1][n-1]for......
  • abc374E Sensor Optimization Dilemma 2
    生产某种产品有N道工序,对于工序i,有S[i]和T[i]两类机器可供选择,机器S[i]单价为P[i],每台每天能处理A[i]件;机器T[i]单价为Q[i],每台每天能处理B[i]件。在不超预算X的前提下,每天最多能生产多少件产品?1<=N<=100;1<=A[i],B[i]<=100;1<=P[i],Q[i],X<=1E7分析:最大产能为所有工序的最小......
  • AtCoder Beginner Contest 373
    ABC373A-September题目传送门代码(签到题)#include<cstdio>#include<cstring>usingnamespacestd;chars[1111];intans;intmain(){for(inti=1;i<=12;++i){scanf("%s",s+1);if(strlen(s+1)==i)++ans;}pr......
  • P1437 [HNOI2004] 敲砖块 题解
    初拿到手感觉限制太多了,不好硬做,于是开始观察。若要取某一个数,我们要取其左上右上两个数,而这两个数又要取上面三个数,所以取一个数的前提条件其实是取这一个三角形。举例2234582712236493比如我要取第3行的6,我首先要取7和12,要取7和12,首先要取3,4,5,所以一层层拓展......
  • AT_abc374_f Shipping
    原题链接不难发现一次发出一定是\(a_i+kx,k\in\mathbb{Z}\)的时刻,因为你一次发出不然就是可以发出抓紧发出,否则肯定是要等到下一次有新包裹要发出再发出,不然你中间等待没意义。也就是说相当于从一个时刻开始连续发送若干次。设\(f_{i,j}\)为在第\(i\)个包裹到达时,总共有......
  • abc370D Cross Explosion
    有H行W列的格子,初始时每个格子中都是墙,接下来有Q组询问,格式为:R[i]C[i],表示在坐标(R[i],C[i])的地方放置炸弹,如果该位置是墙,则墙被炸掉,如果是空地,则上下左右最近的一格墙被炸掉。问最终还剩多少墙?1<=H,W;H*W<=4E5;1<=Q<=2E5;1<=R[i]<=H;1<=C[i]<=W分析:用set维护按行和列的......
  • abc370E Avoid K Partition
    有长度为N的数组A[i]和整数K,需要将A划分成连续子数组,要求每个子数组之和不能为K。问有多少种方案,答案对998244353取模。分析:如果不考虑和不为K的限制,就是个O(n^2)的dp,通过前缀和可以优化成O(n)。现要求子数组和不为K,可以用容斥思想先全部加上,然后减去不符合条件的。#include<bi......
  • abc371E I Hate Sigma Problems
    给定长度为N的数组A[i],记f(l,r)表示区间[l,r]内不同A[i]的个数,求所有子区间f(i,j)之和。1<=N<=2E5,1<=A[i]<=N分析:贡献法,为了方便统计,区间中重复的数字以最左边出现的数为准,保证不重不漏。对于A[i],假设其上一次出现的位置为p,那么包含该数字的左端点可以是p+1,p+2,...,i,右端点可......
  • 130. 被围绕的区域(leetcode)
    https://leetcode.cn/problems/surrounded-regions/classSolution{intn;intm;boolean[][]vis;char[][]board;int[]dx=newint[]{0,1,0,-1};int[]dy=newint[]{1,0,-1,0};publicvoidsolve(char[][]board){//从边缘O出发......
  • 463. 岛屿的周长(leetcode)
    https://leetcode.cn/problems/island-perimeter/description/思路:遍历岛屿,一旦遍历到边界或者水则周长++classSolution{intres;boolean[][]vis;int[][]grid;int[]dx=newint[]{0,1,0,-1};int[]dy=newint[]{1,0,-1,0};intn;intm......