【LeetCode】37. Sudoku Solver 9*9数独求解

一、概述

输入一个9*9的数独,输出该数独的解。注意每一个数独只有唯一解。

第一眼看到这题的时候心里有点懵,因为自己做数独都是先靠眼睛看出哪些地方的可选择数字较少,然后再一个一个试,简而言之就是智能剪枝的DFS算法。但是这个智能剪枝怎么实现呢?怎么才能发现哪里可选择的数字较少?

没法实现,经过我和室友的讨论,分析得知这个数独没法特别好的剪枝。因此只能硬着头皮用DFS了。效果其实不错。

二、分析

由于我的代码还看得过去,因此只分析我自己的代码。

考虑一下纯DFS,每个节点有9个子树,深度为81,那么要是遍历所有节点,则共有9^81条路径。这个数量有点太大了,所以必定要剪枝。剪枝以什么为标准呢?就要看行、列、3*3的小矩阵中的元素是否矛盾,矛盾的话就返回上一层。

这就是中心思想了。听上去很简单,但是要形成这样一个“分析问题→选择算法→DFS→剪枝→哈希表→优化”的思路,还是有点难的。

代码如下:

class Solution {
    int subNum[9][9]=
    {
        {1,1,1,2,2,2,3,3,3},
        {1,1,1,2,2,2,3,3,3},
        {1,1,1,2,2,2,3,3,3},
        {4,4,4,5,5,5,6,6,6},
        {4,4,4,5,5,5,6,6,6},
        {4,4,4,5,5,5,6,6,6},
        {7,7,7,8,8,8,9,9,9},
        {7,7,7,8,8,8,9,9,9},
        {7,7,7,8,8,8,9,9,9}
    };
    int flag=0;
public:
    void solveSudoku(vector<vector<char>>& board) {
        int row[9][10]={0};
        int col[9][10]={0};
        int matrix[9][10]={0};
        for(int i=0;i<9;++i)
            for(int j=0;j<9;++j)
            {
                if(board[i][j]!='.')
                {
                    row[i][board[i][j]-'0']=1;
                    col[j][board[i][j]-'0']=1;
                    matrix[subNum[i][j]-1][board[i][j]-'0']=1;
                }
            }
        DFS(0,0,board,row,col,matrix);
    }
    void DFS(int i,int j,vector<vector<char>>& board,int row[9][10],int col[9][10],int matrix[9][10])
    {
        if(i>8)
        {
            flag=1;
            return;
        }
        else if(board[i][j]!='.')
        {
            if(j<8)
                DFS(i,j+1,board,row,col,matrix);
            else if(j==8)
                DFS(i+1,0,board,row,col,matrix);
        }
        else
        {
            for(int k=1;k<10&&flag==0;k++)
            {
                if(row[i][k]==0&&col[j][k]==0&&matrix[subNum[i][j]-1][k]==0)
                {
                    board[i][j]=k+'0';
                    row[i][k]=1;
                    col[j][k]=1;
                    matrix[subNum[i][j]-1][k]=1;
                    if(j<8)
                        DFS(i,j+1,board,row,col,matrix);
                    else if(j==8)
                        DFS(i+1,0,board,row,col,matrix);
                    if(flag==0)
                    {
                        board[i][j] = '.';
                        row[i][k] = 0;
                        col[j][k] = 0;
                        matrix[subNum[i][j] - 1][k] = 0;
                    }
                }
            }
            return;
        }
    }
};

剪枝我们用的是元素是否重复,因此我们使用三个9*10的哈希表,第一个用来标记每行有没有重复的元素,第二个用来标记每列有没有重复的元素,第三个用来标记每个小矩阵有没有重复的元素。哈希表的行就对应着一行/列/小矩阵。

另外,判断下标来判断当前元素处于第几个小矩阵太麻烦了,因此新建一个9*9的矩阵,标记出各元素位于哪个小矩阵。

接下来,既然哈希表初始化好了,我们选择遍历一次输入数独来为哈希表设置初值。

然后开始递归。

一定要注意以下事实:

对于一个递归函数,它可能用到三种变量:全局变量、参数变量以及函数内变量。

对于全局变量,递归函数对其进行了改动,则无论递归函数return不return,这个改动一直存在。即在下一层中,函数改动了全局变量的值,回到上一层,变量的值不会变成原来的;

对于参数变量,递归函数在下一层对其进行了改动,上一层的变量值不变;

对于函数内变量,也不变。

关键就在于参数变量和全局变量,若参数变量为数组,则其特性和全局变量一样。原因在于,递归的时候,每调用一次函数,就会将其参数全复制一遍,如果是正常的变量,则随便改,不会影响到上一层;如果是数组,则由于传入的是数组头的地址,对数组元素进行修改相当于传参修改,上一层变量的值也会变化。

就如同本题中的哈希表,在第一层中,哈希表的值发生了变化,进入第二层哈希表又发生了变化,如果不作处理,返回上一层的话,哈希表是不会变成原样的,体现出来的bug就是:明明本行没有数字2,数字2对应的哈希表的值为1。

因此总结起来,对于全局变量和参数变量:

如果我们需要每层递归的变量都是独立的,那么最好将其写为参数变量的形式,类似DFS中的i和j,但是如果对变量的修改没法在参数中实现,例如不是+1,而是给数组中的某几个元素赋值,那么就要用全局变量。这时全局变量在返回上层时候要对其修改使其恢复原样。

另外,由于DFS最终会找到一条或多条正确路线,找到后会return。因此,在函数中调用DFS之后的代码,在一层一层返回的时候都会调用。例如上述代码中的

board[i][j] = '.';
row[i][k] = 0;
col[j][k] = 0;
matrix[subNum[i][j] - 1][k] = 0;

这是用于将全局变量变回原值的,但是只有在剪枝的时候才用得到,如果找到正确的路线,它就不要了。因此需要另外设置一个flag,用来判断是否找到正确路线。

三、总结

一道很好的训练递归的题目。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值