LeetCode--37.解数独

解题思路:

        1.获取信息:

                给出一个数独,要求我们解出这个数独,即在空格中填入适当的数

                额外信息:这个数独一定是一个有效的数独,且它的解唯一

                数独的解要满足:

                (1)数字1-9在每一行只能出现一次

                (2)数字1-9在每一列只能出现一次

                (3)数字1-9在每一个以粗实线分割的3X3宫内只能出现一次

        2.分析题目:

                力扣第36题是判断有效数独,我们可以借用一下它的思想,它是通过一次遍历来判断这个数独是否有效,我们取了每一列,每一行,每个小宫格的数字进行比较来判断的

                这道题要解这个数独,如果要填入相关的数字并确保该数独的有效性,那么也就需要这个数独一开始存在的数字来进行判断,所以我们可以使用36题的一次遍历的方法来获取这些一开始就存在的数字

                接下来,我们就需要填充空格了,填入一个数字要考虑该行,该列,该宫格内存在的数字,并且当这个数字填入后,下一个空格可能也需要考虑上一个空格填入的数字是什么

                总的来说,就是每一个空格都可以衍生出多种结果,而每次选择都需要动态规划的思想

                我们可以使用回溯的方法来进行完每一种结果,代码及其优化,我会放在尝试编写代码环节的

        3.示例查验:

                这次的思路,如果要自行用示例来检验一下的话,未免也太过于繁琐,不太合适,所以略过

        4.尝试编写代码:

                (1)回溯法

                        思路:我在分析题目环节时已经大概说过了,就直接写代码了

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        vector<vector<bool>>col(9,vector<bool>(9,false));//每行
        vector<vector<bool>>rol(9,vector<bool>(9,false));//每列
        vector<vector<vector<bool>>>matrix(3,vector<vector<bool>>(3,vector<bool>(9,false)));//每个小宫格
        vector<pair<int,int>>blank;//空格的行标和列标
        for(int i=0;i<9;i++){//一次遍历
            for(int j=0;j<9;j++){
                if(board[i][j]=='.'){
                    blank.push_back({i,j});//存取空格的行标和列标
                }else{
                    int index=board[i][j]-'1';//取出当前位的数字
                    col[i][index]=true;//记录数字在当前行
                    rol[j][index]=true;//记录数字在当前列
                    matrix[i/3][j/3][index]=true;//记录数字在当前宫格
                }
            }
        }
        DFS(col,rol,matrix,blank,board,0);//开始回溯
    }
private:
    bool DFS(vector<vector<bool>>&col,vector<vector<bool>>&rol,vector<vector<vector<bool>>>&matrix,vector<pair<int,int>>&blank,vector<vector<char>>&board,int index){
        if(index==blank.size()){//如果每个空格都被填满,说明中途没有中断,说明是一个有效的数独解
            return true;//返回true
        }
        auto UH=blank[index];//取出空格的位置
        for(int m=0;m<9;m++){//依次看看1-9哪几个数字可以填入空格内
            if(!col[UH.first][m]&&!rol[UH.second][m]&&!matrix[UH.first/3][UH.second/3][m]){//如果某个数字没有出现在该行,该列,该宫格,则可以填入
                board[UH.first][UH.second]='1'+m;//空格内填入数字
                col[UH.first][m]=true;rol[UH.second][m]=true;matrix[UH.first/3][UH.second/3][m]=true;//记录下填入的数字在该行,该列,该宫格
                if(DFS(col,rol,matrix,blank,board,index+1))return true;//如果下一个空格的循环返回是true,则返回true,(这个是确保找到了有效数独后,就直接中断回溯)
                col[UH.first][m]=false;rol[UH.second][m]=false;matrix[UH.first/3][UH.second/3][m]=false;//移除填入的数字在该行,该列,该宫格的记录
            }
        }
        return false;//如果没有数字可以填入,则返回false
    }
};

                (2)位运算优化

                        优化思路:(其实我是看力扣题解获得的优化,现在来给你讲解一下)

                        我们看一下我们的代码,如果要优化的话,那么该如何优化呢?

                        前面的一次遍历,应该是不能省,那么后面呢?

                        如果我们每次都遍历那么几次来找出每行,每列,每个宫格中存在的数字,就会使时间复杂度比较难看,那么该怎么优化呢?

                        如果只用进行一次运算的时间,就可以知道哪些数字存在,哪些数字不存在就好了

                        唉!还真有一种方法,我们想到二进制,如果用二进制每个位数来记录哪个数字出现了,是否会比较美妙呢?

                        如:0 0100 1000,最前面是符号位,刚好就可以存九个数字,该例子表示,数字4和数字7存在,你是不是有点头绪了

                        那么我们在一次遍历的过程中怎么将某个存在的数字存入这个二进制呢?

                        我们可以使用1,来对它进行左移操作(<<),再进行或运算(|)就可以了

                        但是这样不太全面,我建议使用异或(^),这样的话,连续异或两次的话,相当于没有异或,这样就可以比较灵活

                        现在知道怎么存入某个数字了,而我们在回溯中,最主要的是取出数字来进行操作,那么该怎么取出数字呢?

                        在这个环节,我们只用考虑,哪些数字不存在于该列,该行,该宫格内,再将该数字放入数独中,再进行下一循环即可

                        要判断哪些数字不在并且取出该数字,就需要下面几个步骤

                        (1)取出最末尾的1前的预处理

                                我们前面存入的时候,如果某个数字存在,那么二进制的该位就为1,但是1不太方便我们后续的运算,我们就想办法把1变为0,0变为1,如下

                                int seed = ~ (col[i] | rol[j] | matrix[i/3][j/3]) & 0x1ff;

                                其中,0x1ff是十六进制,化为二进制就是1 1 1 1 1 1 1 1 1

                        (2)取出最末尾的1

                                我们知道负数在计算机中是以补码的形式进行存储的,原码要转化为补码,就要除符号位以外,所有位数取反再加1

                                如果我们使用么某个数去与它的负数进行与运算 (&),会发生什么,当然是可以取到最末尾的1了,如下

                                int seeding = seed & (-seed);

                        (3)最末尾1所在的位数就是可填入空格的数字

                                我们上面的步骤会帮助我们取出一个二进制如0 0000 1000的数,它只会有1个1,所以我们这一步就是取出那个可填入的数,(你也可以将一次遍历的填入过程看作一次编码,那这三步就是一个反编码的过程)

                                int FindWeb(int seeding){
                                        for(int i=0;i<9;i++){
                                            if(seeding==(1<<i))return i;
                                        }
                                return 9;
                                }

好了,大概优化思路就说完了,以下就是完整代码了

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {//里面的一次遍历过程,我就不说了
        vector<int> col(9,0);
        vector<int> rol(9,0);
        vector<vector<int>> matrix(3,vector<int>(3,0));
        vector<pair<int,int>>blank;
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]!='.'){
                    int web=board[i][j]-'1';
                    filp(i,j,web,col,rol,matrix);
                }else blank.push_back({i,j});
            }
        }
        DFS(board,0,col,rol,matrix,blank);//进入回溯
    }
private:
    void filp(int i,int j,int web,vector<int>&col,vector<int>&rol,vector<vector<int>>&matrix){//填入数字或删除数字(这里就像我们在第一步时说的那个我的建议)
        col[i]^=(1<<web);
        rol[j]^=(1<<web);
        matrix[i/3][j/3]^=(1<<web);
    }
    int FindWeb(int seeding){//取出数字
        for(int i=0;i<9;i++){
            if(seeding==(1<<i))return i;
        }
        return 9;
    }
    bool DFS(vector<vector<char>>&board,int index,vector<int>&col,vector<int>&rol,vector<vector<int>>&matrix,vector<pair<int,int>>&blank){
        if(index==blank.size())return true;
        int i=blank[index].first;
        int j=blank[index].second;
        int seed=~(col[i]|rol[j]|matrix[i/3][j/3])&0x1ff;
        while(seed){
            int seeding=seed&(-seed);
            int web=FindWeb(seeding);
            board[i][j]=web+'1';
            filp(i,j,web,col,rol,matrix);
            if(DFS(board,index+1,col,rol,matrix,blank))return true;
            filp(i,j,web,col,rol,matrix);
            seed&=(seed-1);
        }
        return false;
    }
};

OK,今天的每日一题就完成了,最近比较忙,可能会有时中断,但会一直更新的,你别放弃孩子

还是说,纸上得来终觉浅,绝知此事要躬行

哈哈,可以晚安了 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值